├── .bun-version ├── .node-version ├── CODEOWNERS ├── eslint.config.mjs ├── SUPPORT.md ├── tsconfig.json ├── package.json ├── .github ├── workflows │ ├── ci.yml │ ├── ai-issue-assessment.yml │ └── check_dist.yml └── ISSUE_TEMPLATE │ ├── feature-request.yml │ └── bug-report.yml ├── LICENSE.txt ├── prompt_examples ├── bug-review.prompt.yml ├── well-formed.prompt.yml ├── spam-detection.prompt.yml └── ai-slop.prompt.yml ├── src ├── __tests__ │ ├── test_prompts │ │ ├── test-bug.yml │ │ └── test-intake.yml │ └── utils.test.ts ├── ai.ts ├── api.ts ├── types.ts ├── utils.ts └── index.ts ├── SECURITY.md ├── CONTRIBUTING.md ├── .gitignore ├── action.yml ├── CODE_OF_CONDUCT.md ├── README.md └── bun.lock /.bun-version: -------------------------------------------------------------------------------- 1 | 1.2.15 -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 23.0.0 -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Repository CODEOWNERS # 3 | # Order is important! The last matching pattern takes the most precedence. # 4 | ############################################################################ 5 | 6 | # Default owners, unless a later match takes precedence. 7 | @github/product-operations -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import eslint from "@eslint/js"; 4 | import tseslint from "typescript-eslint"; 5 | import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; 6 | 7 | export default tseslint.config( 8 | eslint.configs.recommended, 9 | eslintPluginPrettierRecommended, 10 | tseslint.configs.recommended, 11 | { 12 | ignores: ["dist/*", "node_modules/*", "*.config.js"], 13 | }, 14 | ); 15 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new issue. 6 | 7 | For help or questions about using this project, please file an issue. 8 | 9 | This project is under active development and maintained by GitHub staff and the community. We will do our best to respond to support, feature requests, and community questions in a timely manner. 10 | 11 | ## GitHub Support Policy 12 | 13 | Support for this project is limited to the resources listed above. -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Enable latest features 4 | "lib": ["ESNext", "DOM"], 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleDetection": "force", 8 | "jsx": "react-jsx", 9 | "allowJs": true, 10 | 11 | // Bundler mode 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "noEmit": true, 16 | 17 | // Best practices 18 | "strict": true, 19 | "skipLibCheck": true, 20 | "noFallthroughCasesInSwitch": true, 21 | 22 | // Some stricter flags (disabled by default) 23 | "noUnusedLocals": false, 24 | "noUnusedParameters": false, 25 | "noPropertyAccessFromIndexSignature": false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ai-issue-assessment-commenter", 3 | "module": "src/index.ts", 4 | "type": "module", 5 | "devDependencies": { 6 | "@types/bun": "latest", 7 | "@eslint/js": "^9.18.0", 8 | "@types/js-yaml": "^4.0.9", 9 | "eslint": "^9.18.0", 10 | "eslint-plugin-prettier": "^5.2.1", 11 | "eslint-config-prettier": "^9.1.0", 12 | "typescript-eslint": "^8.21.0" 13 | }, 14 | "peerDependencies": { 15 | "typescript": "^5.0.0" 16 | }, 17 | "dependencies": { 18 | "@actions/core": "^1.11.1", 19 | "@actions/github": "^6.0.0", 20 | "@azure-rest/ai-inference": "^1.0.0-beta.6", 21 | "@azure/core-auth": "^1.9.0", 22 | "js-yaml": "^4.1.0" 23 | }, 24 | "scripts": { 25 | "start": "bun run ./src/index.ts", 26 | "lint": "npx eslint .", 27 | "lintFix": "npx eslint . --fix", 28 | "build": "npx eslint . --fix && rm -rf dist/ && bun build src/index.ts --outdir dist/ --target node" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | test-javascript: 16 | name: JavaScript Tests 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - name: Checkout 21 | id: checkout 22 | uses: actions/checkout@v4 23 | 24 | - name: Setup Node.js 25 | id: setup-node 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version-file: .node-version 29 | 30 | - name: bun setup 31 | id: setup-bun 32 | uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.0 33 | 34 | - name: Install dependencies for the action 35 | id: install-deps 36 | run: bun install --frozen-lockfile 37 | 38 | - name: Lint 39 | id: bun-lint 40 | run: bun run lint 41 | 42 | - name: Test 43 | id: bun-ci-test 44 | run: bun test 45 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright GitHub, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /prompt_examples/bug-review.prompt.yml: -------------------------------------------------------------------------------- 1 | messages: 2 | - role: system 3 | content: >+ 4 | You are a world-class product manager that will help decide whether a particular bug report is completely filled out and able to start being worked on by a team member. 5 | 1. Given a bug report analyze it for the following key elements: a clear description of the problem, steps to reproduce, expected versus actual behavior, and any relevant visual proof. 6 | 2. Rate each element provided in the report as `complete`, `incomplete`, or `unable to determine`, except for Screenshots (which are optional). Justify the rating by explaining what is missing or unclear in each element. 7 | 3. The title of the response should be based on the overall completeness rating of all the provided elements. For example: "### AI Assessment: Ready for Review" if complete, "### AI Assessment: Missing Details" if incomplete, or "### AI Assessment: Unsure" if unable to determine. 8 | 4. When determining the overall completeness rating DO NOT include the Screenshots or relevant visual proof section. This section is more of a "nice to have" versus "hard requirement" and it should be ignored. 9 | - role: user 10 | content: '{{input}}' 11 | model: openai/gpt-4o-mini 12 | modelParameters: 13 | max_tokens: 2000 14 | testData: [] 15 | evaluators: [] -------------------------------------------------------------------------------- /src/__tests__/test_prompts/test-bug.yml: -------------------------------------------------------------------------------- 1 | messages: 2 | - role: system 3 | content: >+ 4 | You are a world-class product manager that will help decide whether a particular bug report is completely filled out and able to start being worked on by a team member. 5 | 1. Given a bug report analyze it for the following key elements: a clear description of the problem, steps to reproduce, expected versus actual behavior, and any relevant visual proof. 6 | 2. Rate each element provided in the report as `complete`, `incomplete`, or `unable to determine` except for Screenshots if included. Justify the rating by explaining what is missing or unclear in each element. 7 | 3. The title of the response should be based on the overall completeness rating of all the provided elements. For example: "### AI Assessment: Ready for Review" if complete, "### AI Assessment: Missing Details" if incomplete, or "### AI Assessment: Unsure" if unable to determine. 8 | 4. When determining the overall completeness rating do not include the Screenshots or relevant visual proof section. This section is more of a "nice to have" versus "hard requirement" and it should be ignored. 9 | - role: user 10 | content: '{{input}}' 11 | model: openai/gpt-4o-mini 12 | modelParameters: 13 | max_tokens: 100 14 | testData: [] 15 | evaluators: [] 16 | -------------------------------------------------------------------------------- /.github/workflows/ai-issue-assessment.yml: -------------------------------------------------------------------------------- 1 | name: AI Issue Assessment 2 | 3 | on: 4 | issues: 5 | types: 6 | - labeled 7 | 8 | jobs: 9 | ai-issue-assessment: 10 | if: github.event.label.name == 'request ai review' 11 | runs-on: ubuntu-latest 12 | permissions: 13 | issues: write 14 | models: read 15 | contents: read 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | 21 | - name: Setup Node.js environment 22 | uses: actions/setup-node@v4 23 | 24 | - name: Run AI assessment for issue labeled 25 | id: ai-assessment-issue-labeled 26 | uses: github/ai-assessment-comment-labeler@747e4e5735eaa4dbdbe47d9e9f7697c85f8201f9 # v1.0.0 27 | with: 28 | token: ${{ secrets.GITHUB_TOKEN }} 29 | issue_number: ${{ github.event.issue.number }} 30 | issue_body: ${{ github.event.issue.body }} 31 | repo_name: ${{ github.repository.name }} 32 | owner: ${{ github.repository_owner }} 33 | ai_review_label: 'request ai review' 34 | prompts_directory: 'prompt_examples' 35 | assessment_regex_pattern: '^###.*[aA]ssessment:\s*(.+)$' 36 | no_comment_regex_pattern: '' 37 | no_comment_regex_flags: 'i' 38 | labels_to_prompts_mapping: | 39 | prompt:bug,bug-review.prompt.yml| 40 | prompt:well-formed,well-formed.prompt.yml| 41 | prompt:ai-slop,ai-slop.prompt.yml| 42 | prompt:spam-detection,spam-detection.prompt.yml 43 | -------------------------------------------------------------------------------- /src/ai.ts: -------------------------------------------------------------------------------- 1 | import * as core from "@actions/core"; 2 | import ModelClient, { isUnexpected } from "@azure-rest/ai-inference"; 3 | import { AzureKeyCredential } from "@azure/core-auth"; 4 | import type { AiInferenceFn } from "./types"; 5 | 6 | export const aiInference: AiInferenceFn = async ({ 7 | systemPromptMsg, 8 | endpoint, 9 | modelName, 10 | maxTokens, 11 | token, 12 | content, 13 | }) => { 14 | try { 15 | console.log("AI configuration:"); 16 | console.log(`Endpoint: ${endpoint}`); 17 | console.log(`Model: ${modelName}`); 18 | console.log(`Max Tokens: ${maxTokens}`); 19 | const client = ModelClient(endpoint, new AzureKeyCredential(token)); 20 | 21 | const response = await client.path("/chat/completions").post({ 22 | body: { 23 | messages: [ 24 | { 25 | role: "system", 26 | content: systemPromptMsg, 27 | }, 28 | { role: "user", content }, 29 | ], 30 | max_tokens: maxTokens, 31 | model: modelName, 32 | }, 33 | }); 34 | 35 | if (isUnexpected(response)) { 36 | if (response.body.error) { 37 | throw response.body.error; 38 | } 39 | throw new Error( 40 | "An error occurred while fetching the response (" + 41 | response.status + 42 | "): " + 43 | response.body, 44 | ); 45 | } 46 | 47 | const modelResponse: string | null = 48 | response.body.choices[0].message.content; 49 | 50 | return modelResponse ?? undefined; 51 | } catch (error) { 52 | // Fail the workflow run if an error occurs 53 | if (error instanceof Error) { 54 | core.setFailed(error.message); 55 | } else { 56 | core.setFailed("An unexpected error occurred"); 57 | } 58 | } 59 | }; 60 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | Thanks for helping make GitHub safe for everyone. 2 | 3 | # Security 4 | 5 | GitHub takes the security of our software products and services seriously, including all of the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub). 6 | 7 | Even though [open source repositories are outside of the scope of our bug bounty program](https://bounty.github.com/index.html#scope) and therefore not eligible for bounty rewards, we will ensure that your finding gets passed along to the appropriate maintainers for remediation. 8 | 9 | ## Reporting Security Issues 10 | 11 | If you believe you have found a security vulnerability in any GitHub-owned repository, please report it to us through coordinated disclosure. 12 | 13 | **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** 14 | 15 | Instead, please send an email to opensource-security[@]github.com. 16 | 17 | Please include as much of the information listed below as you can to help us better understand and resolve the issue: 18 | 19 | * The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) 20 | * Full paths of source file(s) related to the manifestation of the issue 21 | * The location of the affected source code (tag/branch/commit or direct URL) 22 | * Any special configuration required to reproduce the issue 23 | * Step-by-step instructions to reproduce the issue 24 | * Proof-of-concept or exploit code (if possible) 25 | * Impact of the issue, including how an attacker might exploit the issue 26 | 27 | This information will help us triage your report more quickly. 28 | 29 | ## Policy 30 | 31 | See [GitHub's Safe Harbor Policy](https://docs.github.com/en/site-policy/security-policies/github-bug-bounty-program-legal-safe-harbor#1-safe-harbor-terms) -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request Guide for Issues 2 | description: A guide to help you define the Definition of Done for a feature request issue. 3 | labels: ["prompt:well-formed", "prompt:ai-slop", "prompt:spam-detection", "request ai review"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | A standardized template for the Definition of Done (DoD) to guide engineers in creating issues with clear and measurable completion criteria. 9 | This template is typically used for larger batch work type of issues. 10 | - type: textarea 11 | id: summary 12 | attributes: 13 | label: "⚒️ Summary" 14 | description: "TL;DR summary of the request." 15 | placeholder: ex. This issue outlines a request to enhance the user experience by addressing current tooling limitations... 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: problemStatement 20 | attributes: 21 | label: "🚩 Problem Statement" 22 | description: "What is the specific issue or challenge that needs to be addressed?" 23 | placeholder: ex. Currently, the biggest hurdle blocking progress in the is the lack of... 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: proposedSolution 28 | attributes: 29 | label: "💡 Proposed Solution" 30 | description: How will this issue be resolved? What actions are required to address the problem? 31 | placeholder: ex. We propose to update tooling solutions that will support the desired user experience... 32 | validations: 33 | required: true 34 | - type: textarea 35 | id: definitionOfDone 36 | attributes: 37 | label: "✅ Definition of Done" 38 | description: "Clear and measurable success metric or deliverable" 39 | value: | 40 | 1. 41 | 2. 42 | 3. 43 | validations: 44 | required: true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: A guide to help you create a bug report with clear and measurable completion criteria. 3 | labels: ["prompt:bug", "prompt:ai-slop", "prompt:spam-detection", "request ai review"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | A standardized template to guide engineers in creating bug reports with clear and measurable completion criteria. 9 | This template is intended to help you track bugs that need to be fixed. 10 | - type: textarea 11 | id: description 12 | attributes: 13 | label: "🚩 Description" 14 | description: A concise explanation of the bug in a few words, including the context in which it occurs. 15 | placeholder: ex. AI review action workflow is failing to complete successfully... 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: stepsToReproduce 20 | attributes: 21 | label: "🔍 Steps to Reproduce" 22 | description: A step-by-step guide to reproduce the bug, including any relevant details. 23 | placeholder: ex. 1. Go to the issues page for owner/team 2. Create a new intake issue... 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: expectedBehavior 28 | attributes: 29 | label: "✅ Expected Behavior" 30 | description: A clear explanation of what was supposed to happen and what actually occurred, highlighting the discrepancy. 31 | placeholder: ex. The AI review action workflow should complete successfully and leave a comment on the issue. Currently no comment is added to the issue... 32 | validations: 33 | required: true 34 | - type: textarea 35 | id: screenshots 36 | attributes: 37 | label: "🖼️ Screenshots" 38 | description: Any relevant screenshots or images that help illustrate the bug. 39 | placeholder: ex. ![screenshot](https://example.com/screenshot.png) -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | [fork]: https://github.com/github/ai-assessment-comment-labeler/fork 4 | [pr]: https://github.com/github/ai-assessment-comment-labeler/compare 5 | [style]: https://github.com/github/ai-assessment-comment-labeler/blob/main/eslint.config.mjs 6 | 7 | Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. 8 | 9 | Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE.txt). 10 | 11 | Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. 12 | 13 | ## Submitting a pull request 14 | 15 | 1. [Fork][fork] and clone the repository 16 | 1. Configure and install the dependencies: `bun install` 17 | 1. Make sure the tests pass on your machine: `bun test` 18 | 1. Make sure linter passes on your machine: `bun run lint|bun run lintFix` 19 | 1. Create a new branch: `git checkout -b my-branch-name` 20 | 1. Make your change, add tests, and make sure the tests and linter still pass 21 | 1. Make a new build: `bun run build` 22 | 1. Push to your fork and [submit a pull request][pr] 23 | 1. Pat yourself on the back and wait for your pull request to be reviewed and merged. 24 | 25 | Here are a few things you can do that will increase the likelihood of your pull request being accepted: 26 | 27 | - Follow the [style guide][style]. 28 | - Write tests. 29 | - Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. 30 | - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 31 | 32 | ## Resources 33 | 34 | - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) 35 | - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) 36 | - [GitHub Help](https://help.github.com) -------------------------------------------------------------------------------- /src/__tests__/test_prompts/test-intake.yml: -------------------------------------------------------------------------------- 1 | messages: 2 | - role: system 3 | content: >+ 4 | You are a world-class product manager that will help decide whether a 5 | particular Request is in alignment with the Team charter. 6 | 7 | 1. Review the request that is given to you. 8 | 9 | 2. Use the Team charter to give feedback on how aligned it is. 10 | 11 | 3. Determine whether or not the request is `aligned`, `not-aligned`, or 12 | `neutral` based on the alignment with each of the following four strategic 13 | trade-offs: 14 | - **Scalable > bespoke**: Scalable, customizable solutions **over** individual or team-specific solutions. 15 | - **GitHub + AI > scratch-built tools**: Build with GitHub (and improve GitHub if that doesn't work) and AI **over** building from scratch and by hand. 16 | - **Bold > safe**: Try something new and ambitious (including unproven technologies) **over** doing what everyone else is doing. 17 | - **Ship to learn > plan to plan**: Fast experimentation and research **over** long-term, large programs - as in “ship to learn” a core GitHub value. 18 | 4. The title of the response should be based on the overall alignment of 19 | all the strategic trade-offs. For example: "### Alignment Assessment: 20 | Aligned", "### Alignment Assessment: Neutral", or "### Alignment 21 | Assessment: Not-aligned" 22 | 23 | 5. Give feedback on how the request might be more in alignment with the 24 | charter. 25 | 26 | 27 | Charter: Our mission is to reduce inefficiencies, eliminate repetitive 28 | toil, and enable higher velocity. We will empower teams with smarter 29 | processes and tools by building innovative automation solutions. By 30 | thinking big and experimenting ambitiously with cutting-edge technologies, 31 | we’re here to make your work easier, faster, and more impactful in ways 32 | that differentiate us from broader Core Ops initiatives. 33 | 34 | - role: user 35 | content: '{{input}}' 36 | model: openai/gpt-4o-mini 37 | modelParameters: 38 | max_tokens: 100 39 | testData: [] 40 | evaluators: [] 41 | -------------------------------------------------------------------------------- /src/api.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | CreateIssueCommentFn, 3 | GetIssueLabelsFn, 4 | AddIssueLabelsFn, 5 | RemoveIssueLabelFn, 6 | } from "./types"; 7 | 8 | export const createIssueComment: CreateIssueCommentFn = async ({ 9 | octokit, 10 | owner, 11 | repo, 12 | issueNumber: issue_number, 13 | body, 14 | }) => { 15 | try { 16 | const response = await octokit.rest.issues.createComment({ 17 | owner, 18 | repo, 19 | issue_number, 20 | body, 21 | }); 22 | if (response.status === 201) { 23 | console.log("Comment created successfully:", response.data.html_url); 24 | return true; 25 | } else { 26 | console.error("Failed to create comment:", response.status); 27 | return false; 28 | } 29 | } catch (error) { 30 | console.error("Error creating issue comment:", error); 31 | return false; 32 | } 33 | }; 34 | 35 | export const getIssueLabels: GetIssueLabelsFn = async ({ 36 | octokit, 37 | owner, 38 | repo, 39 | issueNumber: issue_number, 40 | }) => { 41 | try { 42 | const response = await octokit.rest.issues.listLabelsOnIssue({ 43 | owner, 44 | repo, 45 | issue_number, 46 | }); 47 | return response.data.map((label) => label.name); 48 | } catch (error) { 49 | console.error("Error listing labels on issue:", error); 50 | } 51 | }; 52 | 53 | export const addIssueLabels: AddIssueLabelsFn = async ({ 54 | octokit, 55 | owner, 56 | repo, 57 | issueNumber: issue_number, 58 | labels, 59 | }) => { 60 | try { 61 | await octokit.rest.issues.addLabels({ owner, repo, issue_number, labels }); 62 | } catch (error) { 63 | console.error("Error adding labels to issue:", error); 64 | } 65 | }; 66 | 67 | export const removeIssueLabel: RemoveIssueLabelFn = async ({ 68 | octokit, 69 | owner, 70 | repo, 71 | issueNumber: issue_number, 72 | label, 73 | }) => { 74 | try { 75 | await octokit.rest.issues.removeLabel({ 76 | owner, 77 | repo, 78 | issue_number, 79 | name: label, 80 | }); 81 | console.log(`Label "${label}" removed from issue #${issue_number}`); 82 | } catch (error) { 83 | console.error("Error removing labels from issue:", error); 84 | } 85 | }; 86 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { GitHub } from "@actions/github/lib/utils"; 2 | 3 | export type AiInferenceFn = ( 4 | params: AiInferenceParams, 5 | ) => Promise; 6 | 7 | interface AiInferenceParams { 8 | systemPromptMsg: string; 9 | endpoint: string; 10 | modelName: string; 11 | maxTokens: number; 12 | token: string; 13 | content: string; 14 | } 15 | 16 | export interface Label { 17 | name: string; 18 | description?: string; 19 | } 20 | 21 | export interface YamlData { 22 | messages: { 23 | role: "system" | "user"; 24 | content: string; 25 | }[]; 26 | model: string; 27 | modelParameters: { 28 | max_tokens: number; 29 | }; 30 | } 31 | 32 | export interface GetPromptFileFromLabelsParams { 33 | issueLabels: Label[]; 34 | labelsToPromptsMapping: string; 35 | } 36 | export interface WriteActionSummaryParams { 37 | promptFile: string; 38 | aiResponse: string; 39 | assessmentLabel: string; 40 | } 41 | 42 | export type GetPromptOptions = ( 43 | promptFile: string, 44 | promptsDirectory: string, 45 | ) => { 46 | systemMsg: string; 47 | model: string; 48 | maxTokens: number; 49 | }; 50 | 51 | export type CreateIssueCommentFn = ( 52 | params: CreateCommentParams, 53 | ) => Promise; 54 | 55 | interface CreateCommentParams { 56 | octokit: InstanceType; 57 | owner: string; 58 | repo: string; 59 | issueNumber: number; 60 | body: string; 61 | } 62 | 63 | export type AddIssueLabelsFn = (params: AddLabelsParams) => Promise; 64 | 65 | interface AddLabelsParams { 66 | octokit: InstanceType; 67 | owner: string; 68 | repo: string; 69 | issueNumber: number; 70 | labels: string[]; 71 | } 72 | 73 | export type RemoveIssueLabelFn = (params: RemoveLabelParams) => Promise; 74 | 75 | interface RemoveLabelParams { 76 | octokit: InstanceType; 77 | owner: string; 78 | repo: string; 79 | issueNumber: number; 80 | label: string; 81 | } 82 | 83 | export type GetIssueLabelsFn = ( 84 | params: GetLabelsParams, 85 | ) => Promise; 86 | 87 | interface GetLabelsParams { 88 | octokit: InstanceType; 89 | owner: string; 90 | repo: string; 91 | issueNumber: number; 92 | } 93 | -------------------------------------------------------------------------------- /.github/workflows/check_dist.yml: -------------------------------------------------------------------------------- 1 | # In JavaScript actions, `dist/` is a special directory. When you reference 2 | # an action with the `uses:` property, `dist/index.js` is the code that will be 3 | # run. For this project, the `dist/index.js` file is transpiled from other 4 | # source files. This workflow ensures the `dist/` directory contains the 5 | # expected transpiled code. 6 | # 7 | # If this workflow is run from a feature branch, it will act as an additional CI 8 | # check and fail if the checked-in `dist/` directory does not match what is 9 | # expected from the build. 10 | name: Check Transpiled JavaScript 11 | 12 | on: 13 | pull_request: 14 | branches: 15 | - main 16 | push: 17 | branches: 18 | - main 19 | 20 | permissions: 21 | contents: read 22 | 23 | jobs: 24 | check-dist: 25 | name: Check dist/ 26 | runs-on: ubuntu-latest 27 | 28 | steps: 29 | # Checkout the repository. 30 | - name: Checkout 31 | id: checkout 32 | uses: actions/checkout@v4 33 | 34 | # Setup Node.js using the version specified in `.node-version`. 35 | - name: Setup Node.js 36 | id: setup-node 37 | uses: actions/setup-node@v4 38 | with: 39 | node-version-file: .node-version 40 | 41 | - name: bun setup 42 | id: setup-bun 43 | uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.0 44 | with: 45 | bun-version-file: ".bun-version" 46 | 47 | # Install dependencies using the lock file. 48 | - name: Install dependencies for the action 49 | id: install-deps 50 | run: bun install --frozen-lockfile 51 | 52 | # Build the `dist/` directory. 53 | - name: Build dist/ Directory 54 | id: build 55 | run: bun run build 56 | 57 | # This will fail the workflow if the `dist/` directory is different than expected. 58 | - name: Compare Directories 59 | id: diff 60 | run: | 61 | if [ ! -d dist/ ]; then 62 | echo "Expected dist/ directory does not exist. See status below:" 63 | ls -la ./ 64 | exit 1 65 | fi 66 | if [ "$(git diff --ignore-space-at-eol --text dist/ | wc -l)" -gt "0" ]; then 67 | echo "Detected uncommitted changes after build. See status below:" 68 | git diff --ignore-space-at-eol --text dist/ 69 | exit 1 70 | fi 71 | 72 | # If `dist/` was different than expected, upload the expected version as a workflow artifact. 73 | - if: ${{ failure() && steps.diff.outcome == 'failure' }} 74 | name: Upload Artifact 75 | id: upload 76 | uses: actions/upload-artifact@v4 77 | with: 78 | name: dist 79 | path: dist/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore 2 | 3 | # Logs 4 | 5 | logs 6 | _.log 7 | npm-debug.log_ 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Caches 14 | 15 | .cache 16 | 17 | # Diagnostic reports (https://nodejs.org/api/report.html) 18 | 19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 20 | 21 | # Runtime data 22 | 23 | pids 24 | _.pid 25 | _.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | 34 | coverage 35 | *.lcov 36 | 37 | # nyc test coverage 38 | 39 | .nyc_output 40 | 41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 42 | 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | 47 | bower_components 48 | 49 | # node-waf configuration 50 | 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | 55 | build/Release 56 | 57 | # Dependency directories 58 | 59 | node_modules/ 60 | jspm_packages/ 61 | 62 | # Snowpack dependency directory (https://snowpack.dev/) 63 | 64 | web_modules/ 65 | 66 | # TypeScript cache 67 | 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | 72 | .npm 73 | 74 | # Optional eslint cache 75 | 76 | .eslintcache 77 | 78 | # Optional stylelint cache 79 | 80 | .stylelintcache 81 | 82 | # Microbundle cache 83 | 84 | .rpt2_cache/ 85 | .rts2_cache_cjs/ 86 | .rts2_cache_es/ 87 | .rts2_cache_umd/ 88 | 89 | # Optional REPL history 90 | 91 | .node_repl_history 92 | 93 | # Output of 'npm pack' 94 | 95 | *.tgz 96 | 97 | # Yarn Integrity file 98 | 99 | .yarn-integrity 100 | 101 | # dotenv environment variable files 102 | 103 | .env 104 | .env.development.local 105 | .env.test.local 106 | .env.production.local 107 | .env.local 108 | 109 | # parcel-bundler cache (https://parceljs.org/) 110 | 111 | .parcel-cache 112 | 113 | # Next.js build output 114 | 115 | .next 116 | out 117 | 118 | # Nuxt.js build / generate output 119 | 120 | .nuxt 121 | # dist 122 | 123 | # Gatsby files 124 | 125 | # Comment in the public line in if your project uses Gatsby and not Next.js 126 | 127 | # https://nextjs.org/blog/next-9-1#public-directory-support 128 | 129 | # public 130 | 131 | # vuepress build output 132 | 133 | .vuepress/dist 134 | 135 | # vuepress v2.x temp and cache directory 136 | 137 | .temp 138 | 139 | # Docusaurus cache and generated files 140 | 141 | .docusaurus 142 | 143 | # Serverless directories 144 | 145 | .serverless/ 146 | 147 | # FuseBox cache 148 | 149 | .fusebox/ 150 | 151 | # DynamoDB Local files 152 | 153 | .dynamodb/ 154 | 155 | # TernJS port file 156 | 157 | .tern-port 158 | 159 | # Stores VSCode versions used for testing VSCode extensions 160 | 161 | .vscode-test 162 | 163 | # yarn v2 164 | 165 | .yarn/cache 166 | .yarn/unplugged 167 | .yarn/build-state.yml 168 | .yarn/install-state.gz 169 | .pnp.* 170 | 171 | # IntelliJ based IDEs 172 | .idea 173 | 174 | # Finder (MacOS) folder config 175 | .DS_Store 176 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'AI Assessment Comment Labeler' 2 | description: Generate an AI comment based on a prompt file and labels. 3 | 4 | branding: 5 | icon: 'message-square' 6 | color: orange 7 | 8 | inputs: 9 | token: 10 | description: The token to use 11 | required: true 12 | ai_review_label: 13 | description: The label applied to the issue to trigger AI review 14 | required: true 15 | issue_number: 16 | description: The issue number to comment on 17 | required: true 18 | issue_body: 19 | description: The body of the issue to comment on 20 | required: true 21 | prompts_directory: 22 | description: The path to the prompts directory where the .prompt.yml files are located 23 | required: true 24 | labels_to_prompts_mapping: 25 | description: "A mapping of labels to prompt files, separated by '|'. Format: 'label1,prompt1.prompt.yml|label2,prompt2.prompt.yml'" 26 | required: true 27 | model: 28 | description: The model to use for AI generation. Will be inferred from the .prompt.yml file if not provided. Action will fail if not found in the file and not provided. 29 | required: false 30 | endpoint: 31 | description: 'The endpoint to use. Defaults to the OpenAI API endpoint "https://models.github.ai/inference"' 32 | required: false 33 | default: 'https://models.github.ai/inference' 34 | max_tokens: 35 | description: 'The maximum number of tokens to generate. Will be inferred from the .prompt.yml file if not provided. Defaults to 200 if not found in the file.' 36 | required: false 37 | repo_name: 38 | description: The name of the repository. Will be inferred from the GitHub context if not provided. 39 | required: false 40 | owner: 41 | description: The owner of the repository. Will be inferred from the GitHub context if not provided. 42 | required: false 43 | assessment_regex_pattern: 44 | description: 'Regex pattern for capturing the assessment line in the AI response used for creating the label to add to the issue. Default: "^###.*[aA]ssessment:\s*(.+)$"' 45 | required: false 46 | default: '^###.*[aA]ssessment:\s*(.+)$' 47 | assessment_regex_flags: 48 | description: 'Regex flags for the assessment regex pattern. e.g.: "i" for case-insensitive matching.' 49 | required: false 50 | default: '' 51 | no_comment_regex_pattern: 52 | description: 'Regex pattern for capturing the no comment directive in the AI response. e.g.: ""' 53 | required: false 54 | default: '' 55 | no_comment_regex_flags: 56 | description: 'Regex flags for the no comment regex pattern. e.g.: "i" for case-insensitive matching.' 57 | required: false 58 | default: '' 59 | suppress_labels: 60 | description: 'Do not add any labels to the issue. Useful if you just want to get the AI assessments and labels as outputs.' 61 | required: false 62 | default: 'false' 63 | suppress_comments: 64 | description: 'Do not add any comments to the issue. Useful if you just want to get the AI assessments and labels as outputs.' 65 | required: false 66 | default: 'false' 67 | outputs: 68 | ai_assessments: 69 | description: 'JSON array of objects representing all assessments made by the AI e.g. "[{"prompt": "security.prompt.yml", "assessmentLabel": "ai:security:high risk", "response": "### Assessment: High Risk\nThe code contains..."}]"' 70 | runs: 71 | using: node20 72 | main: dist/index.js 73 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at . All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /prompt_examples/well-formed.prompt.yml: -------------------------------------------------------------------------------- 1 | messages: 2 | - role: system 3 | content: >+ 4 | You are an assistant tasked with evaluating a feature request submitted to a team inside GitHub. Your job is to review the request and determine whether it is **well-formed** or not. A well-formed request is one that provides sufficient context for the team to triage and evaluate the request, enabling them to decide if they want to take it on based on timing, resources, and strategic alignment. 5 | 6 | To evaluate the request, follow these steps: 7 | 8 | 1. **Assess Clarity and Completeness**: 9 | - Has the requester clearly described the feature they are asking for? 10 | - Does the request explain the problem it intends to solve or the value it would provide? 11 | - Are there specific details that would help the team evaluate feasibility (e.g., use cases, requirements, technical specifics, or examples)? 12 | - Does the request explain how this feature aligns with the team’s goals or broader strategy? 13 | 14 | 2. **Identify Missing Information (if any)**: 15 | - If the request is missing critical information, list the specific questions or details the team would need to better understand and evaluate the request. 16 | 17 | 3. **Provide Feedback**: 18 | - If the request **is well-formed**, affirm this and summarize the strengths of the request. 19 | - If the request **is not well-formed**, politely explain why, and provide a list of clear, actionable recommendations for improving the request. 20 | 21 | 4. The title of the response should be based on whether it is well formed or not. For example: "### Well-formed Assessment: Yes" or "### Well-formed Assessment: Needs more information" 22 | 23 | 5. If the assessment is in the affirmative then add "" to the body of the response. 24 | 25 | --- 26 | 27 | **Output Format:** 28 | 29 | ### Well-formed Assessment: (Yes/Needs more information) 30 | 31 | 1. **Is the feature request well-formed?** (Yes/No) 32 | 33 | 2. **Evaluation Summary**: 34 | - [Provide a brief high-level summary of your assessment.] 35 | 36 | 3. **Feedback/Actionable Recommendations**: 37 | - If well-formed: 38 | - [Briefly state why it’s well-formed, add "" at the end of the response.] 39 | - If not well-formed: 40 | - [List key pieces of missing information or improvements needed.] 41 | 42 | --- 43 | 44 | **Example Input:** 45 | 46 | _Feature Request:_ 47 | 48 | "I think we should add a dark mode to the app. It would make things look cooler and maybe some users would like it." 49 | 50 | **Example Output:** 51 | 52 | ### Well-formed Assessment: Needs more information 53 | 54 | 1. **Is the feature request well-formed?** No 55 | 56 | 2. **Evaluation Summary**: 57 | The feature request is not well-formed. While it suggests adding a "dark mode" to the app, it lacks sufficient context about the problem it aims to solve, the specific user needs it addresses, or how it aligns with team goals or priorities. 58 | 59 | 3. **Feedback/Actionable Recommendations**: 60 | - Clearly describe the problem that "dark mode" intends to solve (e.g., easier usability in low-light environments, user requests for personalization, etc.). 61 | - Provide evidence or examples of why this feature would add value (e.g., feedback from users, analytics, industry benchmarks). 62 | - Include any technical considerations if known (e.g., platforms to support, specific UI/UX constraints). 63 | - Explain how the feature aligns with the team’s objectives or strategy. 64 | - role: user 65 | content: '{{input}}' 66 | model: openai/gpt-4o-mini 67 | modelParameters: 68 | max_tokens: 2000 69 | testData: [] 70 | evaluators: [] 71 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { summary } from "@actions/core"; 2 | import * as fs from "fs"; 3 | import * as path from "path"; 4 | import yaml from "js-yaml"; 5 | import type { 6 | GetPromptFileFromLabelsParams, 7 | GetPromptOptions, 8 | WriteActionSummaryParams, 9 | YamlData, 10 | } from "./types"; 11 | 12 | const MAX_TOKENS = 200; 13 | 14 | export const getRegexFromString = ( 15 | regexString: string, 16 | regexFlags: string, 17 | ): RegExp => { 18 | let regex; 19 | try { 20 | regex = new RegExp(regexString, regexFlags); 21 | console.log("Debug: Constructed regex:", regex); 22 | } catch (error) { 23 | throw new Error( 24 | `Invalid regex pattern or flags provided: pattern="${regexString}", flags="${regexFlags}". Error: ${error}`, 25 | ); 26 | } 27 | return regex; 28 | }; 29 | 30 | export const writeActionSummary = ({ 31 | promptFile, 32 | aiResponse, 33 | assessmentLabel, 34 | }: WriteActionSummaryParams) => { 35 | summary 36 | .addHeading("Assessment Result") 37 | .addHeading("Assessment") 38 | .addCodeBlock(assessmentLabel) 39 | .addHeading("Prompt File") 40 | .addCodeBlock(promptFile) 41 | .addHeading("Details") 42 | .addCodeBlock(aiResponse) 43 | .write(); 44 | }; 45 | 46 | export const getBaseFilename = (promptFile: string): string => 47 | promptFile.replace(/\.prompt\.y.*ml$/, ""); 48 | 49 | export const getAILabelAssessmentValue = ( 50 | promptFile: string, 51 | aiResponse: string, 52 | assessmentRegex: RegExp, 53 | ): string => { 54 | const fileName = getBaseFilename(promptFile); 55 | const lines = aiResponse.split("\n"); 56 | let assessment = `ai:${fileName}:unsure`; 57 | 58 | for (const line of lines) { 59 | const match = line.match(assessmentRegex); 60 | if (match && match[1]) { 61 | const matchedAssessment = match[1].trim().toLowerCase(); 62 | console.log(`Assessment found: ${matchedAssessment}`); 63 | if (matchedAssessment) { 64 | assessment = `ai:${fileName}:${matchedAssessment}`; 65 | } 66 | } 67 | } 68 | // Max 50 characters for labels 69 | return assessment.slice(0, 50); 70 | }; 71 | 72 | export const getPromptFilesFromLabels = ({ 73 | issueLabels, 74 | labelsToPromptsMapping, 75 | }: GetPromptFileFromLabelsParams): string[] => { 76 | const promptFiles = []; 77 | const labelsToPromptsMappingArr = labelsToPromptsMapping.split("|"); 78 | for (const labelPromptMapping of labelsToPromptsMappingArr) { 79 | const labelPromptArr = labelPromptMapping.split(",").map((s) => s.trim()); 80 | const labelMatch = issueLabels.some( 81 | (label) => label?.name == labelPromptArr[0], 82 | ); 83 | if (labelMatch) { 84 | promptFiles.push(labelPromptArr[1]); 85 | } 86 | } 87 | 88 | return promptFiles; 89 | }; 90 | 91 | export const getPromptOptions: GetPromptOptions = ( 92 | promptFile, 93 | promptsDirectory, 94 | ) => { 95 | const fileContents = fs.readFileSync( 96 | path.resolve(process.cwd(), promptsDirectory, promptFile), 97 | "utf-8", 98 | ); 99 | if (!fileContents) { 100 | throw new Error(`System prompt file not found: ${promptFile}`); 101 | } 102 | // Parse the YAML content 103 | try { 104 | const yamlData = yaml.load(fileContents) as YamlData; 105 | if (!yamlData || !Array.isArray(yamlData?.messages)) { 106 | throw new Error("Invalid YAML format in the prompt file"); 107 | } 108 | // Find the system message 109 | const systemMsg = yamlData.messages.find( 110 | (msg: { role: string }) => msg.role === "system", 111 | ); 112 | if (!systemMsg || !systemMsg.content) { 113 | throw new Error("System message not found in the prompt file"); 114 | } 115 | // Return the content of the system message 116 | return { 117 | systemMsg: systemMsg.content, 118 | model: yamlData?.model, 119 | maxTokens: yamlData?.modelParameters?.max_tokens || MAX_TOKENS, 120 | }; 121 | } catch (error) { 122 | if (error instanceof Error) { 123 | throw new Error("Unable to parse system prompt file: " + error.message); 124 | } else { 125 | throw new Error("Unable to parse system prompt file"); 126 | } 127 | } 128 | }; 129 | -------------------------------------------------------------------------------- /prompt_examples/spam-detection.prompt.yml: -------------------------------------------------------------------------------- 1 | messages: 2 | - role: system 3 | content: >+ 4 | You are a spam detection system for GitHub issues and comments. Your job is to analyze the provided content and determine if it contains spam, including both general spam content and suspicious links. 5 | 6 | Consider these spam indicators: 7 | 8 | **General Spam Indicators:** 9 | - Promotional content or advertisements 10 | - Repetitive text patterns or excessive use of emojis/caps 11 | - Low-quality, nonsensical, or gibberish content 12 | - Requests for personal information (email, phone, bank details) 13 | - Cryptocurrency, gambling, or get-rich-quick schemes 14 | - Content that doesn't relate to the repository's purpose or software development 15 | - Excessive claims about money-making opportunities 16 | - Urgency tactics ("LIMITED TIME", "ACT NOW", etc.) 17 | 18 | **Link Spam Indicators:** 19 | - Multiple unrelated links that don't serve the discussion 20 | - Links to promotional, gambling, or adult content websites 21 | - Short URL services used to hide destinations (bit.ly, tinyurl, etc.) 22 | - Links to cryptocurrency or financial scam sites 23 | - Suspicious domains or newly registered domains 24 | - Links to download executables or suspicious files 25 | - Links that completely unrelated to the repository or issue topic 26 | 27 | **Legitimate Content Indicators:** 28 | - Links to official documentation, Stack Overflow, or related GitHub repositories 29 | - Technical discussions relevant to the repository 30 | - Bug reports with specific details 31 | - Feature requests with clear use cases 32 | - Constructive feedback or suggestions 33 | 34 | --- 35 | 36 | **Output Format:** 37 | 38 | ### Spam Detection Assessment: (Spam/Legitimate) 39 | 40 | 1. **Is this content spam?** (Yes/No) 41 | - [If No this is not considered spam, then add ""] 42 | 43 | 2. **Analysis Summary**: 44 | - [Provide a brief high-level summary of your assessment.] 45 | 46 | 3. **Detailed Reasoning**: 47 | - If Spam: 48 | - [List specific spam indicators found.] 49 | - [Reference which categories of indicators were considered in your analysis] 50 | - If Legitimate: 51 | - [Briefly explain why the content appears legitimate and add "" to the body of the response.] 52 | 53 | --- 54 | 55 | - role: user 56 | content: '{{input}}' 57 | model: openai/gpt-4o-mini 58 | modelParameters: 59 | max_tokens: 1000 60 | testData: 61 | - input: >- 62 | 🚀 AMAZING OPPORTUNITY! 🚀 Make $1000 daily from home! 💰 Contact me for 63 | exclusive crypto trading secrets! Send your email and phone number! 64 | expected: "Spam Detection Assessment: Spam" 65 | - input: >- 66 | Check out this amazing deal! Get 50% off crypto trading at 67 | bit.ly/cryptodeal and also visit tinyurl.com/easymoney for more 68 | opportunities! 69 | expected: "Spam Detection Assessment: Spam" 70 | - input: >- 71 | FREE MONEY! FREE MONEY! FREE MONEY! Click here now! Send me your bank 72 | details and I'll transfer $10,000 immediately! This is not a scam! 73 | expected: "Spam Detection Assessment: Spam" 74 | - input: >- 75 | asdfjaslkdfj alskdjflaskdjf random text here askljdflasd qwerty qwerty 76 | qwerty nonsense content here 77 | expected: "Spam Detection Assessment: Spam" 78 | - input: >- 79 | I'm experiencing the same issue described in this bug report. The 80 | application crashes when I try to import large CSV files. Any suggestions 81 | for a workaround? 82 | expected: "Spam Detection Assessment: Legitimate" 83 | - input: >- 84 | Hey, I found a solution to this issue in the official documentation: 85 | https://github.com/owner/repo/wiki/troubleshooting 86 | expected: "Spam Detection Assessment: Legitimate" 87 | - input: >- 88 | Thanks for the fix! This resolves the memory leak I was seeing in 89 | production. The performance improvement is significant. 90 | expected: "Spam Detection Assessment: Legitimate" 91 | - input: >- 92 | You might want to check the Stack Overflow discussion about this: 93 | https://stackoverflow.com/questions/12345/similar-issue 94 | expected: "Spam Detection Assessment: Legitimate" 95 | evaluators: [] 96 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { context, getOctokit } from "@actions/github"; 2 | import { getInput, setOutput } from "@actions/core"; 3 | import { aiInference } from "./ai"; 4 | import { 5 | getPromptFilesFromLabels, 6 | getAILabelAssessmentValue, 7 | writeActionSummary, 8 | getPromptOptions, 9 | getRegexFromString, 10 | getBaseFilename, 11 | } from "./utils"; 12 | import { 13 | getIssueLabels, 14 | createIssueComment, 15 | addIssueLabels, 16 | removeIssueLabel, 17 | } from "./api"; 18 | import type { Label } from "./types"; 19 | 20 | const main = async () => { 21 | // Required inputs 22 | const token = getInput("token") || process.env.GITHUB_TOKEN; 23 | const owner = getInput("owner") || context?.repo?.owner; 24 | const repo = getInput("repo_name") || context?.repo?.repo; 25 | 26 | const issueNumber = getInput("issue_number") 27 | ? parseInt(getInput("issue_number"), 10) 28 | : context?.payload?.issue?.number; 29 | const issueBody = getInput("issue_body"); 30 | 31 | const promptsDirectory = getInput("prompts_directory"); 32 | const aiReviewLabel = getInput("ai_review_label"); 33 | const labelsToPromptsMapping = getInput("labels_to_prompts_mapping"); 34 | 35 | const assessmentRegexPattern = getInput("assessment_regex_pattern"); 36 | const assessmentRegexFlags = getInput("assessment_regex_flags"); 37 | 38 | const noCommentRegexPattern = getInput("no_comment_regex_pattern"); 39 | const noCommentRegexFlags = getInput("no_comment_regex_flags"); 40 | 41 | const aiAssessmentRegex = getRegexFromString( 42 | assessmentRegexPattern, 43 | assessmentRegexFlags, 44 | ); 45 | const noCommentRegex = noCommentRegexPattern 46 | ? getRegexFromString(noCommentRegexPattern, noCommentRegexFlags) 47 | : null; 48 | 49 | if ( 50 | !token || 51 | !owner || 52 | !repo || 53 | !issueNumber || 54 | !issueBody || 55 | !promptsDirectory || 56 | !aiReviewLabel || 57 | !labelsToPromptsMapping 58 | ) { 59 | throw new Error("Required inputs are not set"); 60 | } 61 | 62 | const octokit = getOctokit(token); 63 | 64 | // AI configuration 65 | const endpoint = getInput("endpoint"); 66 | const modelName = getInput("model"); 67 | const maxTokens = getInput("max_tokens") 68 | ? parseInt(getInput("max_tokens"), 10) 69 | : undefined; 70 | 71 | // Optional suppressing inputs 72 | const suppressLabelsInput = getInput("suppress_labels") == "true"; 73 | const suppressCommentsInput = getInput("suppress_comments") == "true"; 74 | 75 | // Get Labels from the issue 76 | let issueLabels: Label[] = context?.payload?.issue?.labels ?? []; 77 | if (!issueLabels || issueLabels.length === 0) { 78 | const labels = await getIssueLabels({ 79 | octokit, 80 | owner, 81 | repo, 82 | issueNumber, 83 | }); 84 | if (labels) { 85 | issueLabels = labels.map((name) => ({ name })) as Label[]; 86 | } else { 87 | console.log("No labels found on the issue."); 88 | return; 89 | } 90 | } 91 | 92 | // Check if the issue requires AI review based on the aiReviewLabel 93 | const requireAiReview = issueLabels.some( 94 | (label) => label?.name == aiReviewLabel, 95 | ); 96 | if (!requireAiReview) { 97 | console.log( 98 | `No AI review required. Issue does not have label: ${aiReviewLabel}`, 99 | ); 100 | return; 101 | } 102 | 103 | // Remove the aiReviewLabel trigger label 104 | console.log(`Removing label: ${aiReviewLabel}`); 105 | await removeIssueLabel({ 106 | octokit, 107 | owner, 108 | repo, 109 | issueNumber, 110 | label: aiReviewLabel, 111 | }); 112 | 113 | // Get Prompt file based on issue labels and mapping 114 | const promptFiles = getPromptFilesFromLabels({ 115 | issueLabels, 116 | labelsToPromptsMapping, 117 | }); 118 | 119 | if (promptFiles.length === 0) { 120 | console.log( 121 | "No matching prompt files found. No issue labels matched the configured label-to-prompt mapping. " + 122 | "To run an AI assessment, add a label that corresponds to a prompt file configured in your workflow.", 123 | ); 124 | return; 125 | } 126 | 127 | const labelsToAdd: string[] = []; 128 | const outPutAssessments = []; 129 | for (const promptFile of promptFiles) { 130 | console.log(`Using prompt file: ${promptFile}`); 131 | const promptOptions = getPromptOptions(promptFile, promptsDirectory); 132 | 133 | const aiResponse = await aiInference({ 134 | token, 135 | content: issueBody, 136 | systemPromptMsg: promptOptions.systemMsg, 137 | endpoint: endpoint, 138 | maxTokens: maxTokens || promptOptions.maxTokens, 139 | modelName: modelName || promptOptions.model, 140 | }); 141 | if (aiResponse) { 142 | if ( 143 | suppressCommentsInput || 144 | (noCommentRegex && noCommentRegex.test(aiResponse)) 145 | ) { 146 | console.log("No comment creation as per AI response directive."); 147 | } else { 148 | const commentCreated = await createIssueComment({ 149 | octokit, 150 | owner, 151 | repo, 152 | issueNumber, 153 | body: aiResponse, 154 | }); 155 | if (!commentCreated) { 156 | throw new Error("Failed to create comment"); 157 | } 158 | } 159 | 160 | // Add the assessment label to the issue 161 | const assessmentLabel = getAILabelAssessmentValue( 162 | promptFile, 163 | aiResponse, 164 | aiAssessmentRegex, 165 | ); 166 | labelsToAdd.push(assessmentLabel); 167 | 168 | writeActionSummary({ 169 | promptFile, 170 | aiResponse, 171 | assessmentLabel, 172 | }); 173 | outPutAssessments.push({ 174 | prompt: promptFile, 175 | assessmentLabel, 176 | response: aiResponse, 177 | }); 178 | } else { 179 | console.log("No response received from AI."); 180 | const fileName = getBaseFilename(promptFile); 181 | labelsToAdd.push(`ai:${fileName}:unable-to-process`); 182 | } 183 | } 184 | 185 | setOutput("ai_assessments", JSON.stringify(outPutAssessments)); 186 | 187 | if (suppressLabelsInput) { 188 | console.log("Label suppression is enabled. No labels will be added."); 189 | return; 190 | } 191 | 192 | if (labelsToAdd.length > 0) { 193 | console.log(`Adding labels: ${labelsToAdd.join(", ")}`); 194 | await addIssueLabels({ 195 | octokit, 196 | owner, 197 | repo, 198 | issueNumber, 199 | labels: labelsToAdd, 200 | }); 201 | } else { 202 | console.log("No labels to add found."); 203 | } 204 | }; 205 | 206 | if (process.env.NODE_ENV !== "test") { 207 | main(); 208 | } 209 | -------------------------------------------------------------------------------- /prompt_examples/ai-slop.prompt.yml: -------------------------------------------------------------------------------- 1 | messages: 2 | - role: system 3 | content: >+ 4 | You are an assistant tasked with evaluating whether content submitted in an issue or request constitutes "AI slop" - low-quality media made using generative artificial intelligence technology, characterized by an inherent lack of effort and being generated at overwhelming volume. 5 | 6 | --- 7 | 8 | **Definition of AI Slop:** 9 | 10 | AI slop is low-quality content (text, images, or other media) created using generative AI that exhibits: 11 | - Inherent lack of effort in creation 12 | - Overwhelming volume or mass production characteristics 13 | - Priority of speed and quantity over substance and quality 14 | - "Incredibly banal, realistic style" that's easy to process but lacks depth 15 | - Content that feels like "digital clutter" or "filler content" 16 | 17 | **Key Indicators of AI Slop:** 18 | 19 | 1. **Content Quality Issues:** 20 | - Generic, template-like language with no personal touch 21 | - Repetitive phrases or structures 22 | - Nonsensical or contradictory information 23 | - Obvious factual errors or impossible scenarios 24 | - Excessive verbosity without added value 25 | 26 | 2. **Visual Content Issues (if applicable):** 27 | - Anatomical impossibilities (extra fingers, malformed body parts) 28 | - Inconsistent lighting, shadows, or perspective 29 | - Text/logos with spelling errors or garbled letters 30 | - Unrealistic combinations of elements 31 | - Overly perfect or artificial-looking imagery 32 | 33 | 3. **Content Purpose and Context:** 34 | - Appears designed solely for engagement farming 35 | - Lacks clear purpose or actionable information 36 | - Contains obvious attempts to manipulate emotions without substance 37 | - Shows signs of mass production or template usage 38 | - Content seems disconnected from genuine human need or problem 39 | 40 | 4. **Linguistic Patterns:** 41 | - Overuse of superlatives without justification 42 | - Generic conclusions or recommendations 43 | - Lack of specific, concrete details 44 | - Patterns suggesting non-native speaker using translation tools 45 | - Inconsistent tone or style within the same content 46 | 47 | **Important Distinctions:** 48 | 49 | - **AI slop is NOT:** Thoughtfully created content that happens to use AI assistance for drafting, editing, or enhancement where human judgment and effort are clearly applied 50 | - **AI slop IS:** Content that appears to be mass-produced with minimal human oversight, lacks genuine purpose, or prioritizes volume over quality 51 | 52 | --- 53 | 54 | **Evaluation Steps:** 55 | 56 | 1. **Analyze Content Quality:** 57 | - Does the content provide genuine value or insight? 58 | - Is there evidence of human thought, experience, or expertise? 59 | - Are there specific, actionable details rather than generic statements? 60 | 61 | 2. **Check for AI Slop Indicators:** 62 | - Look for the quality issues, visual problems, and linguistic patterns listed above 63 | - Consider whether the content feels mass-produced or template-based 64 | - Evaluate if the content serves a genuine purpose beyond engagement 65 | 66 | 3. **Consider Context and Intent:** 67 | - Does the submission align with legitimate project needs? 68 | - Is there evidence of thoughtful consideration in the request? 69 | - Would this content be useful to actual humans with real problems? 70 | 71 | 4. **Make Assessment:** 72 | - Clearly categorize as AI slop or legitimate content 73 | - Provide specific examples of what led to your conclusion 74 | - If borderline, explain the reasoning and suggest improvements 75 | 76 | The title of the response should indicate the assessment result: "### AI Slop Assessment: Detected" or "### AI Slop Assessment: Not detected" 77 | 78 | If the assessment result is in the negative meaning "Not detected" then add "" to the body of the response. 79 | 80 | --- 81 | 82 | **Output Format:** 83 | 84 | ### AI Slop Assessment: (AI Slop Detected/AI Slop Not detected) 85 | 86 | 1. **Is this content AI slop?** (Yes/No) 87 | 88 | 2. **Assessment Summary:** 89 | - [Provide a brief explanation of your determination] 90 | 91 | 3. **Evidence and Reasoning:** 92 | - **If AI slop detected:** 93 | - List specific indicators found (quality issues, linguistic patterns, etc.) 94 | - Explain why this appears to be low-effort, mass-produced content 95 | - Note any obvious signs of AI generation without human oversight 96 | - **If not AI slop:** 97 | - Highlight evidence of human thought, effort, or genuine purpose 98 | - Note specific, valuable details or insights provided 99 | - Explain why this appears to be legitimate, thoughtful content 100 | 101 | 4. **Recommendations:** 102 | - **If AI slop:** Suggest the submitter provide more specific, thoughtful, and human-generated content 103 | - **If not AI slop:** Acknowledge the quality and substance of the submission and add "" to the body of the response. 104 | 105 | --- 106 | 107 | **Example Input:** 108 | 109 | _Submission:_ 110 | "We need to revolutionize our amazing platform with cutting-edge AI solutions that will dramatically transform user experience and boost engagement through innovative features that leverage next-generation technology to create unprecedented value and deliver outstanding results that exceed expectations." 111 | 112 | **Example Output:** 113 | 114 | ### AI Slop Assessment: Detected 115 | 116 | 1. **Is this content AI slop?** Yes 117 | 118 | 2. **Assessment Summary:** 119 | This submission exhibits classic AI slop characteristics with generic, buzzword-heavy language that provides no specific information, actionable details, or evidence of genuine human thought. 120 | 121 | 3. **Evidence and Reasoning:** 122 | - Excessive use of superlatives ("amazing," "cutting-edge," "unprecedented") without justification 123 | - Generic tech buzzwords strung together without specific meaning 124 | - No concrete details about actual problems, solutions, or requirements 125 | - Template-like structure that could apply to any product or service 126 | - Lacks evidence of domain knowledge or specific user needs 127 | - Appears designed to sound impressive while saying nothing substantial 128 | 129 | 4. **Recommendations:** 130 | The submitter should provide a specific problem statement, concrete requirements, and evidence of genuine need. Replace generic buzzwords with specific details about what functionality is needed and why. 131 | 132 | - role: user 133 | content: '{{input}}' 134 | model: openai/gpt-4o-mini 135 | modelParameters: 136 | max_tokens: 2000 137 | testData: [] 138 | evaluators: [] 139 | -------------------------------------------------------------------------------- /src/__tests__/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from "bun:test"; 2 | import { 3 | getPromptOptions, 4 | getAILabelAssessmentValue, 5 | getPromptFilesFromLabels, 6 | getRegexFromString, 7 | getBaseFilename, 8 | } from "../utils"; 9 | 10 | describe("getPromptOptions", () => { 11 | it("should return the system content field from test-intake.yml", () => { 12 | const requestIntakePrompt = 13 | 'You are a world-class product manager that will help decide whether a particular Request is in alignment with the Team charter.\n1. Review the request that is given to you.\n2. Use the Team charter to give feedback on how aligned it is.\n3. Determine whether or not the request is `aligned`, `not-aligned`, or `neutral` based on the alignment with each of the following four strategic trade-offs:\n - **Scalable > bespoke**: Scalable, customizable solutions **over** individual or team-specific solutions.\n - **GitHub + AI > scratch-built tools**: Build with GitHub (and improve GitHub if that doesn\'t work) and AI **over** building from scratch and by hand. \n - **Bold > safe**: Try something new and ambitious (including unproven technologies) **over** doing what everyone else is doing.\n - **Ship to learn > plan to plan**: Fast experimentation and research **over** long-term, large programs - as in “ship to learn” a core GitHub value.\n4. The title of the response should be based on the overall alignment of all the strategic trade-offs. For example: "### Alignment Assessment: Aligned", "### Alignment Assessment: Neutral", or "### Alignment Assessment: Not-aligned"\n5. Give feedback on how the request might be more in alignment with the charter.\n\nCharter: Our mission is to reduce inefficiencies, eliminate repetitive toil, and enable higher velocity. We will empower teams with smarter processes and tools by building innovative automation solutions. By thinking big and experimenting ambitiously with cutting-edge technologies, we’re here to make your work easier, faster, and more impactful in ways that differentiate us from broader Core Ops initiatives.\n\n'; 14 | expect( 15 | getPromptOptions("test-intake.yml", "./src/__tests__/test_prompts"), 16 | ).toEqual({ 17 | systemMsg: requestIntakePrompt, 18 | model: "openai/gpt-4o-mini", 19 | maxTokens: 100, 20 | }); 21 | }); 22 | 23 | it("should return the system content field from test-bug.yml", () => { 24 | const bugIntakePrompt = 25 | 'You are a world-class product manager that will help decide whether a particular bug report is completely filled out and able to start being worked on by a team member. 1. Given a bug report analyze it for the following key elements: a clear description of the problem, steps to reproduce, expected versus actual behavior, and any relevant visual proof. 2. Rate each element provided in the report as `complete`, `incomplete`, or `unable to determine` except for Screenshots if included. Justify the rating by explaining what is missing or unclear in each element. 3. The title of the response should be based on the overall completeness rating of all the provided elements. For example: "### AI Assessment: Ready for Review" if complete, "### AI Assessment: Missing Details" if incomplete, or "### AI Assessment: Unsure" if unable to determine. 4. When determining the overall completeness rating do not include the Screenshots or relevant visual proof section. This section is more of a "nice to have" versus "hard requirement" and it should be ignored. \n'; 26 | expect( 27 | getPromptOptions("test-bug.yml", "./src/__tests__/test_prompts"), 28 | ).toEqual({ 29 | systemMsg: bugIntakePrompt, 30 | model: "openai/gpt-4o-mini", 31 | maxTokens: 100, 32 | }); 33 | }); 34 | }); 35 | 36 | describe("getAILabelAssessmentValue", () => { 37 | const aiAssessmentRegex = new RegExp("^###.*assessment:\\s*(.+)$", "i"); 38 | 39 | it("should return 'ai:aligned' for aligned assessment", () => { 40 | const aiResponse = 41 | "### Alignment Assessment: Aligned\nThe request is fully aligned with the team charter."; 42 | expect( 43 | getAILabelAssessmentValue( 44 | "intake-prompt.prompt.yml", 45 | aiResponse, 46 | aiAssessmentRegex, 47 | ), 48 | ).toEqual("ai:intake-prompt:aligned"); 49 | }); 50 | 51 | it("should return 'ai:not aligned' for not aligned assessment", () => { 52 | const aiResponse = 53 | "### Alignment Assessment: Not Aligned\nThe request does not align with the team charter."; 54 | expect( 55 | getAILabelAssessmentValue( 56 | "bug-prompt.prompt.yml", 57 | aiResponse, 58 | aiAssessmentRegex, 59 | ), 60 | ).toEqual("ai:bug-prompt:not aligned"); 61 | }); 62 | 63 | it("should return 'ai:unsure' if no assessment present", () => { 64 | const aiResponse = 65 | "### AI Assessment:\nThe request lacks sufficient information."; 66 | expect( 67 | getAILabelAssessmentValue( 68 | "bug.prompt.yml", 69 | aiResponse, 70 | aiAssessmentRegex, 71 | ), 72 | ).toEqual("ai:bug:unsure"); 73 | }); 74 | 75 | it("should handle 'Alignment Assessment: Neutral' case", () => { 76 | const aiResponse = 77 | "### Alignment Assessment: Neutral\nFeedback on Strategic Trade-offs:"; 78 | expect( 79 | getAILabelAssessmentValue( 80 | "test.prompt.yml", 81 | aiResponse, 82 | aiAssessmentRegex, 83 | ), 84 | ).toEqual("ai:test:neutral"); 85 | }); 86 | 87 | it("should work with the default action regex (case-sensitive)", () => { 88 | const defaultRegex = new RegExp("^###.*[aA]ssessment:\\s*(.+)$", ""); 89 | const aiResponse = "### Alignment Assessment: Neutral"; 90 | expect( 91 | getAILabelAssessmentValue("test.prompt.yml", aiResponse, defaultRegex), 92 | ).toEqual("ai:test:neutral"); 93 | }); 94 | }); 95 | 96 | describe("getPromptFilesFromLabels", () => { 97 | it("should return the request-intake.prompt.yml file for the support request label", () => { 98 | const issueLabels = [ 99 | { name: "request ai review" }, 100 | { name: "support request" }, 101 | ]; 102 | const labelsToPromptsMapping = 103 | "support request,request-intake.prompt.yml|bug,bug-review.prompt.yml"; 104 | 105 | expect( 106 | getPromptFilesFromLabels({ 107 | issueLabels, 108 | labelsToPromptsMapping, 109 | }), 110 | ).toEqual(["request-intake.prompt.yml"]); 111 | }); 112 | 113 | it("should return the bug-review.prompt.yml file for the bug label", () => { 114 | const issueLabels = [{ name: "request ai review" }, { name: "bug" }]; 115 | const labelsToPromptsMapping = 116 | "support request,request-intake.prompt.yml|bug,bug-review.prompt.yml"; 117 | 118 | expect( 119 | getPromptFilesFromLabels({ 120 | issueLabels, 121 | labelsToPromptsMapping, 122 | }), 123 | ).toEqual(["bug-review.prompt.yml"]); 124 | }); 125 | }); 126 | 127 | describe("getRegexFromString", () => { 128 | it("should create a valid regex with pattern and flags", () => { 129 | const regex = getRegexFromString("test", "gi"); 130 | expect(regex).toBeInstanceOf(RegExp); 131 | expect(regex.source).toBe("test"); 132 | expect(regex.flags).toBe("gi"); 133 | }); 134 | 135 | it("should create a regex with no flags", () => { 136 | const regex = getRegexFromString("hello", ""); 137 | expect(regex).toBeInstanceOf(RegExp); 138 | expect(regex.source).toBe("hello"); 139 | expect(regex.flags).toBe(""); 140 | }); 141 | 142 | it("should create a case-insensitive regex", () => { 143 | const regex = getRegexFromString("Assessment", "i"); 144 | expect(regex.test("assessment")).toBe(true); 145 | expect(regex.test("ASSESSMENT")).toBe(true); 146 | }); 147 | 148 | it("should throw error for invalid regex pattern", () => { 149 | expect(() => { 150 | getRegexFromString("[invalid", ""); 151 | }).toThrow(/Invalid regex pattern or flags provided/); 152 | }); 153 | 154 | it("should throw error for invalid regex flags", () => { 155 | expect(() => { 156 | getRegexFromString("valid", "x"); 157 | }).toThrow(/Invalid regex pattern or flags provided/); 158 | }); 159 | 160 | it("should handle complex regex patterns", () => { 161 | const regex = getRegexFromString("^###.*[aA]ssessment:\\s*(.+)$", ""); 162 | expect(regex).toBeInstanceOf(RegExp); 163 | expect(regex.test("### Alignment Assessment: Aligned")).toBe(true); 164 | }); 165 | 166 | it("should handle hidden text regex patterns", () => { 167 | const regex = getRegexFromString("", "gmi"); 168 | expect(regex).toBeInstanceOf(RegExp); 169 | expect( 170 | regex.test("### Well-form: Yes\n\nThis is a test."), 171 | ).toBe(true); 172 | }); 173 | }); 174 | 175 | describe("getBaseFilename", () => { 176 | it("should return the base filename without extension", () => { 177 | const result = getBaseFilename("test.prompt.yml"); 178 | expect(result).toEqual("test"); 179 | }); 180 | 181 | it("should handle files with multiple extensions", () => { 182 | const result = getBaseFilename("test.prompt.yaml"); 183 | expect(result).toEqual("test"); 184 | }); 185 | 186 | it("should return the original filename if no prompt extension", () => { 187 | const result = getBaseFilename("test.txt"); 188 | expect(result).toEqual("test.txt"); 189 | }); 190 | }); 191 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AI Issue Assessment Commenter 2 | 3 | > GitHub Action to automatically assess issues with an AI model, post (or optionally suppress) a structured review comment, and apply standardized AI derived labels based on configurable prompt files. 4 | 5 | ## Table of Contents 6 | - [AI Issue Assessment Commenter](#ai-issue-assessment-commenter) 7 | - [Table of Contents](#table-of-contents) 8 | - [Overview](#overview) 9 | - [How It Works](#how-it-works) 10 | - [Features](#features) 11 | - [Prompt File Schema](#prompt-file-schema) 12 | - [Inputs](#inputs) 13 | - [Label → Prompt Mapping](#label--prompt-mapping) 14 | - [Regex Customization](#regex-customization) 15 | - [Suppressing Labels \& Comments](#suppressing-labels--comments) 16 | - [Behavior Matrix](#behavior-matrix) 17 | - [Example Regex Based Comment Suppression](#example-regex-based-comment-suppression) 18 | - [Example Workflow Setup](#example-workflow-setup) 19 | - [Outputs / Labels Added](#outputs--labels-added) 20 | - [Labels](#labels) 21 | - [Output: `ai_assessments`](#output-ai_assessments) 22 | - [Required Permissions](#required-permissions) 23 | - [Troubleshooting](#troubleshooting) 24 | - [Development](#development) 25 | - [Testing](#testing) 26 | - [Releasing](#releasing) 27 | - [Contributing](#contributing) 28 | - [Security / Reporting Issues](#security--reporting-issues) 29 | - [FAQ](#faq) 30 | - [License](#license) 31 | 32 | ## Overview 33 | This action evaluates newly labeled GitHub Issues using an AI model available through **GitHub Models** (or a compatible endpoint you provide) and: 34 | 1. Selects one or more prompt configuration files based on existing labels applied to the issue. 35 | 2. Runs inference with the chosen model & system prompt. 36 | 3. Extracts an "assessment" value from the AI response (via a configurable regex) and converts it into a standardized label of the form: 37 | `ai::` (lowercased, spaces preserved unless you modify regex or post processing). 38 | 4. Optionally posts the full AI response as a comment (unless a suppression marker is detected). 39 | 5. Removes the trigger label so the process is idempotent and can be retriggered manually by re‑adding it. 40 | 41 | ## How It Works 42 | High level flow: 43 | 1. Issue receives a trigger label (e.g. `request ai review`). 44 | 2. Action runs and gathers all labels applied to the issue. 45 | 3. Each label is checked against your `labels_to_prompts_mapping` list. 46 | 4. For each matched prompt file: 47 | - System prompt + model + max tokens are resolved (overrides from workflow inputs if provided). 48 | - Inference is executed with the issue body as user content. 49 | - Response is scanned: 50 | - Optional "no comment" directive → skip comment. 51 | - Assessment header line → derive label. 52 | - Summary written to the job summary. 53 | 5. All derived labels are added; trigger label is removed. 54 | 55 | ## Features 56 | - Multiple prompt files per issue (supports multifaceted assessments). 57 | - Per prompt inference (each prompt gets its own AI run & resulting label). 58 | - Override model / max tokens at workflow level or rely on prompt file. 59 | - Configurable assessment extraction via regex. 60 | - Configurable comment suppression via regex. 61 | - Clear action summary with raw AI output + derived assessment. 62 | - Works with any model accessible via the GitHub Models API endpoint you specify. 63 | 64 | ## Prompt File Schema 65 | Example `.prompt.yml` file: 66 | ```yaml 67 | messages: 68 | - role: system 69 | content: >+ 70 | You are a world-class product manager that will help decide whether a particular bug report is completely filled out and able to start being worked on by a team member. 71 | 1. Given a bug report analyze it for the following key elements: a clear description of the problem, steps to reproduce, expected versus actual behavior, and any relevant visual proof. 72 | 2. Rate each element provided in the report as `complete`, `incomplete`, or `unable to determine` except for Screenshots if included. Justify the rating by explaining what is missing or unclear in each element. 73 | 3. The title of the response should be based on the overall completeness rating of all the provided elements. For example: "### AI Assessment: Ready for Review" if complete, "### AI Assessment: Missing Details" if incomplete, or "### AI Assessment: Unsure" if unable to determine. 74 | 4. When determining the overall completeness rating do not include the Screenshots or relevant visual proof section. This section is more of a "nice to have" versus "hard requirement" and it should be ignored. 75 | - role: user 76 | content: '{{input}}' 77 | model: openai/gpt-4o-mini 78 | modelParameters: 79 | max_tokens: 100 80 | testData: [] 81 | evaluators: [] 82 | ``` 83 | Required elements: 84 | - `messages`: Must include at least one `system` and one `user` with `{{input}}` placeholder. 85 | - `model`: A model identifier in `{publisher}/{model_name}` format compatible with GitHub Models. 86 | - `modelParameters.max_tokens` (optional) used if workflow input `max_tokens` not provided. 87 | 88 | ## Inputs 89 | Various inputs are defined in [`action.yml`](action.yml): 90 | 91 | | Name | Description | Required | Default | 92 | | :-- | :-- | :-- | :-- | 93 | | `token` | Token for API calls (usually `${{ secrets.GITHUB_TOKEN }}`) | true | `github.token` | 94 | | `ai_review_label` | Label that triggers AI processing | true | | 95 | | `issue_number` | Issue number | true | | 96 | | `issue_body` | Issue body to feed into AI | true | | 97 | | `prompts_directory` | Directory containing `.prompt.yml` files, relative to the root of the project | true | | 98 | | `labels_to_prompts_mapping` | Mapping string `label,prompt.yml\|label2,prompt2.yml` | true | | 99 | | `model` | Override model (falls back to prompt file) | false | | 100 | | `endpoint` | Inference endpoint | false | `https://models.github.ai/inference` | 101 | | `max_tokens` | Override max tokens (else prompt file else 200) | false | 200 | 102 | | `repo_name` | Repository name (auto detected) | false | | 103 | | `owner` | Repository owner (auto detected) | false | | 104 | | `assessment_regex_pattern` | Pattern to capture assessment line | false | `^###.*[aA]ssessment:\s*(.+)$` | 105 | | `assessment_regex_flags` | Flags for assessment regex | false | | 106 | | `no_comment_regex_pattern` | Pattern to detect comment suppression | false | | 107 | | `no_comment_regex_flags` | Flags for suppress pattern | false | | 108 | | `suppress_labels` | If `true`, do not add derived `ai:` labels (still sets output) | false | `false` | 109 | | `suppress_comments` | If `true`, never post AI response comments | false | `false` | 110 | 111 | ## Label → Prompt Mapping 112 | Provide a single string where pairs are separated by `|` and each pair is `label,prompt-file-name`. Example: 113 | ``` 114 | labels_to_prompts_mapping: 'bug,bug-review.prompt.yml|support request,request-intake.prompt.yml|security,security-assessment.prompt.yml' 115 | ``` 116 | Rules: 117 | - Matching is case sensitive to how GitHub returns labels. 118 | - A label may map to only one prompt file (first match used if duplicates are present). 119 | - Multiple labels can each trigger their prompt; all selected prompts are processed. 120 | 121 | ## Regex Customization 122 | Default assessment extraction looks for any markdown header starting with `###` and containing `Assessment:` (case insensitive if you supply `i` flag) and captures the remainder of the line: 123 | ``` 124 | ^###.*[aA]ssessment:\s*(.+)$ 125 | ``` 126 | Example variations: 127 | - Want stricter title: `^### AI Assessment:\s*(.+)$` 128 | - Want to allow other synonyms: `^###.*(Assessment|Alignment):\s*(.+)$` (then handle capture group 2 in post processing—current implementation expects one capture group, so keep a single `(.+)`). 129 | 130 | If your header is like: 131 | ``` 132 | ## Result: Passed 133 | ``` 134 | You could set: 135 | ``` 136 | assessment_regex_pattern: '^## Result:\s*(.+)$' 137 | ``` 138 | 139 | ## Suppressing Labels & Comments 140 | You have three mechanisms to control side‑effects (labels, comments): 141 | 142 | 1. Runtime flags: 143 | - `suppress_comments: true` → Never create an issue comment with the AI response. 144 | - `suppress_labels: true` → Never add the derived `ai::` labels. 145 | 2. Regex directive suppression: 146 | - Provide `no_comment_regex_pattern` (& optional flags) to let the AI itself decide when to skip commenting by emitting a marker inside the response (e.g. an HTML comment token). 147 | 3. Leaving both off (default) → Always attempts to comment (unless regex matches) and always adds labels. 148 | 149 | ### Behavior Matrix 150 | | Setting / Condition | Comment Posted | Labels Added | Output `ai_assessments` | 151 | | ------------------- | -------------- | ------------ | ----------------------- | 152 | | defaults (no suppress flags, no regex match) | Yes | Yes | Yes | 153 | | `suppress_comments: true` | No | Yes | Yes | 154 | | `suppress_labels: true` | Yes (unless regex suppresses) | No | Yes | 155 | | both suppress flags true | No | No | Yes | 156 | | regex match only | No | Yes | Yes | 157 | 158 | Notes: 159 | - The JSON output (`ai_assessments`) is always produced regardless of suppression so you can post‑process in later steps. 160 | - If you rely on regex suppression ensure your system prompt instructs the model precisely when to emit the marker. 161 | 162 | ### Example Regex Based Comment Suppression 163 | Add an instruction in the system prompt to emit a marker when you only want labeling. Example system instruction snippet: 164 | ``` 165 | If the overall assessment is fully ready, append: 166 | ``` 167 | Then configure in the workflow inputs: 168 | ``` 169 | no_comment_regex_pattern: '' 170 | no_comment_regex_flags: 'i' 171 | ``` 172 | When the pattern is found (and `suppress_comments` is not already true), the comment step is skipped; labels (unless `suppress_labels` true) and summary still generated. 173 | 174 | ## Example Workflow Setup 175 | Below is an example workflow file. It triggers whenever a label is added, checks for the trigger label, processes, then removes it. 176 | ```yaml 177 | name: AI Issue Assessment 178 | on: 179 | issues: 180 | types: [labeled] 181 | jobs: 182 | ai-assessment: 183 | if: github.event.label.name == 'request ai review' 184 | runs-on: ubuntu-latest 185 | permissions: 186 | issues: write 187 | models: read 188 | contents: read 189 | steps: 190 | - name: Checkout 191 | uses: actions/checkout@v4 192 | 193 | - name: Setup Node.js 194 | uses: actions/setup-node@v4 195 | 196 | - name: Run AI assessment 197 | id: ai-assessment 198 | uses: github/ai-assessment-comment-labeler@main 199 | with: 200 | token: ${{ secrets.GITHUB_TOKEN }} 201 | issue_number: ${{ github.event.issue.number }} 202 | issue_body: ${{ github.event.issue.body }} 203 | repo_name: ${{ github.event.repository.name }} 204 | owner: ${{ github.repository_owner }} 205 | ai_review_label: 'request ai review' 206 | prompts_directory: './Prompts' 207 | labels_to_prompts_mapping: 'bug,bug-review.prompt.yml|support request,request-intake.prompt.yml' 208 | ``` 209 | Multiple prompts example with custom overrides: 210 | ```yaml 211 | with: 212 | model: openai/gpt-4o-mini 213 | max_tokens: 300 214 | labels_to_prompts_mapping: 'bug,bug-review.prompt.yml|performance,perf-triage.prompt.yml|design,ux-assessment.prompt.yml' 215 | ``` 216 | 217 | ## Outputs / Labels Added 218 | ### Labels 219 | For each prompt file used (e.g. `bug-review.prompt.yml`), the assessment line text (after `Assessment:`) is: 220 | 1. Lowercased 221 | 2. Prefixed with `ai::` where `` is the file name without extension and trailing `-prompt` parts preserved. 222 | 223 | Examples: 224 | - `### AI Assessment: Ready for Review` → `ai:bug-review:ready for review` 225 | - `### AI Assessment: Missing Details` → `ai:bug-review:missing details` 226 | - No header found → `ai:bug-review:unsure` 227 | 228 | These labels let you filter, search, or automate additional workflows. Labels are skipped entirely when `suppress_labels: true`. 229 | 230 | ### Output: `ai_assessments` 231 | The action always sets a structured output named `ai_assessments` containing an array of objects (one per processed prompt) with: 232 | ``` 233 | [ 234 | { 235 | "prompt": "bug-review.prompt.yml", 236 | "assessmentLabel": "ai:bug-review:ready for review", 237 | "response": "### AI Assessment: Ready for Review\n...full model response..." 238 | }, 239 | { 240 | "prompt": "perf-triage.prompt.yml", 241 | "assessmentLabel": "ai:perf-triage:potential regression", 242 | "response": "### AI Assessment: Potential Regression\n..." 243 | } 244 | ] 245 | ``` 246 | Use this for downstream steps regardless of whether you suppressed labels or comments. Example consumption in a workflow step: 247 | ```yaml 248 | - name: Parse Results 249 | uses: actions/github-script@v7 250 | env: 251 | ASSESSMENT_OUTPUT: ${{ steps.ai-assessment.outputs.ai_assessments }} 252 | with: 253 | script: | 254 | const assessments = JSON.parse(process.env.ASSESSMENT_OUTPUT); 255 | for (const assessment of assessments) { 256 | console.log(`Prompt File: ${assessment.prompt}`); 257 | console.log(`Label: ${assessment.assessmentLabel}`); 258 | console.log(`AI Response: ${assessment.response}`); 259 | core.summary.addRaw(`***Prompt File*:** ${assessment.prompt}\n**Label:** ${assessment.assessmentLabel}\n**AI Response:** ${assessment.response}\n\n`); 260 | } 261 | core.summary.write(); 262 | ``` 263 | You can also feed this JSON to later automation (e.g. create a summary table, open follow-up issues, trigger notifications). 264 | 265 | ## Required Permissions 266 | Recommended minimal permissions block: 267 | ```yaml 268 | permissions: 269 | issues: write # create comment & add/remove labels 270 | models: read # access GitHub Models inference 271 | contents: read # read prompt files 272 | ``` 273 | 274 | ## Troubleshooting 275 | | Symptom | Likely Cause | Fix | 276 | | --- | --- | --- | 277 | | Action exits early: "Required inputs are not set" | Missing mandatory input | Ensure all required `with:` fields are present | 278 | | "No matching prompt files found." | Issue doesn't have a label that maps to a prompt | Add a label that corresponds to one in your `labels_to_prompts_mapping` (e.g., `bug`, `support request`), and ensure your prompts folder path is configured relative to repo root | 279 | | No labels added | Assessment regex failed | Adjust `assessment_regex_pattern` / flags | 280 | | Comment missing | Suppression regex matched | Remove or modify `no_comment_regex_pattern` | 281 | | Fallback label `unsure` | No header matched regex | Update system prompt to ensure header form | 282 | | Model error | Unsupported or misspelled model | Verify model exists in GitHub Models catalog | 283 | | 404 prompt file | Wrong `prompts_directory` path | Ensure path relative to repo root | 284 | 285 | Enable debug logs by setting in workflow: 286 | ``` 287 | env: 288 | ACTIONS_STEP_DEBUG: true 289 | ``` 290 | (Requires enabling debug logging in repository settings.) 291 | 292 | ## Development 293 | Local development steps: 294 | ```bash 295 | # Install dependencies 296 | bun install 297 | 298 | # Lint 299 | bun run lint 300 | 301 | # Auto fix + build dist 302 | bun run build 303 | 304 | # Run locally (requires env vars if outside GitHub Actions) 305 | GITHUB_TOKEN=ghp_xxx ISSUE_NUMBER=1 bun run src/index.ts 306 | ``` 307 | Key scripts (`package.json`): `lint`, `lintFix`, `build`. 308 | The action bundles to `dist/index.js` (Node 20 runtime). 309 | 310 | ### Testing 311 | Basic tests live under `src/__tests__`. Add additional parsing / utility tests as needed. 312 | 313 | ### Releasing 314 | - Update version tag or reference a commit SHA in downstream workflows. 315 | - Optionally create a Git tag & release notes summarizing changes. 316 | 317 | ## Contributing 318 | See [`CONTRIBUTING.md`](CONTRIBUTING.md) & follow the code of conduct in [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md). 319 | 320 | ## Security / Reporting Issues 321 | For vulnerability disclosures follow [`SECURITY.md`](SECURITY.md). Please do not open public issues for sensitive reports. 322 | 323 | ## FAQ 324 | **Q: Can I run multiple prompts in one execution?** Yes, any label in the mapping that matches the issue produces a separate inference & label. 325 | 326 | **Q: How do I force a re-run?** Re-add the trigger label. 327 | 328 | **Q: Can I use a completely different header phrase?** Yes, adjust `assessment_regex_pattern` to capture the desired line; the first capture group is used as the assessment value. 329 | 330 | **Q: Can I trim / normalize spaces?** Current implementation lowercases assessment as is. You can post process by adding a follow up workflow reacting to `labeled` events. 331 | 332 | **Q: Will it modify existing AI labels?** It only adds new labels; it does not remove prior `ai:` labels. Clean up logic can be added in a future enhancement. 333 | 334 | ## License 335 | See [`LICENSE.txt`](LICENSE.txt). 336 | -------------------------------------------------------------------------------- /bun.lock: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "workspaces": { 4 | "": { 5 | "name": "ai-issue-assessment-commenter", 6 | "dependencies": { 7 | "@actions/core": "^1.11.1", 8 | "@actions/github": "^6.0.0", 9 | "@azure-rest/ai-inference": "^1.0.0-beta.6", 10 | "@azure/core-auth": "^1.9.0", 11 | "js-yaml": "^4.1.0", 12 | }, 13 | "devDependencies": { 14 | "@eslint/js": "^9.18.0", 15 | "@types/bun": "latest", 16 | "@types/js-yaml": "^4.0.9", 17 | "eslint": "^9.18.0", 18 | "eslint-config-prettier": "^9.1.0", 19 | "eslint-plugin-prettier": "^5.2.1", 20 | "typescript-eslint": "^8.21.0", 21 | }, 22 | "peerDependencies": { 23 | "typescript": "^5.0.0", 24 | }, 25 | }, 26 | }, 27 | "packages": { 28 | "@actions/core": ["@actions/core@1.11.1", "", { "dependencies": { "@actions/exec": "^1.1.1", "@actions/http-client": "^2.0.1" } }, "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A=="], 29 | 30 | "@actions/exec": ["@actions/exec@1.1.1", "", { "dependencies": { "@actions/io": "^1.0.1" } }, "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w=="], 31 | 32 | "@actions/github": ["@actions/github@6.0.1", "", { "dependencies": { "@actions/http-client": "^2.2.0", "@octokit/core": "^5.0.1", "@octokit/plugin-paginate-rest": "^9.2.2", "@octokit/plugin-rest-endpoint-methods": "^10.4.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "undici": "^5.28.5" } }, "sha512-xbZVcaqD4XnQAe35qSQqskb3SqIAfRyLBrHMd/8TuL7hJSz2QtbDwnNM8zWx4zO5l2fnGtseNE3MbEvD7BxVMw=="], 33 | 34 | "@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="], 35 | 36 | "@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="], 37 | 38 | "@azure-rest/ai-inference": ["@azure-rest/ai-inference@1.0.0-beta.6", "", { "dependencies": { "@azure-rest/core-client": "^2.1.0", "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-lro": "^2.7.2", "@azure/core-rest-pipeline": "^1.18.2", "@azure/core-tracing": "^1.2.0", "@azure/logger": "^1.1.4", "tslib": "^2.8.1" } }, "sha512-j5FrJDTHu2P2+zwFVe5j2edasOIhqkFj+VkDjbhGkQuOoIAByF0egRkgs0G1k03HyJ7bOOT9BkRF7MIgr/afhw=="], 39 | 40 | "@azure-rest/core-client": ["@azure-rest/core-client@2.5.0", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.9.0", "@azure/core-rest-pipeline": "^1.5.0", "@azure/core-tracing": "^1.0.1", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-KMVIPxG6ygcQ1M2hKHahF7eddKejYsWTjoLIfTWiqnaj42dBkYzj4+S8rK9xxmlOaEHKZHcMrRbm0NfN4kgwHw=="], 41 | 42 | "@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], 43 | 44 | "@azure/core-auth": ["@azure/core-auth@1.10.0", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-util": "^1.11.0", "tslib": "^2.6.2" } }, "sha512-88Djs5vBvGbHQHf5ZZcaoNHo6Y8BKZkt3cw2iuJIQzLEgH4Ox6Tm4hjFhbqOxyYsgIG/eJbFEHpxRIfEEWv5Ow=="], 45 | 46 | "@azure/core-lro": ["@azure/core-lro@2.7.2", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-util": "^1.2.0", "@azure/logger": "^1.0.0", "tslib": "^2.6.2" } }, "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw=="], 47 | 48 | "@azure/core-rest-pipeline": ["@azure/core-rest-pipeline@1.22.0", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.8.0", "@azure/core-tracing": "^1.0.1", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.0.0", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-OKHmb3/Kpm06HypvB3g6Q3zJuvyXcpxDpCS1PnU8OV6AJgSFaee/covXBcPbWc6XDDxtEPlbi3EMQ6nUiPaQtw=="], 49 | 50 | "@azure/core-tracing": ["@azure/core-tracing@1.3.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-+XvmZLLWPe67WXNZo9Oc9CrPj/Tm8QnHR92fFAFdnbzwNdCH1h+7UdpaQgRSBsMY+oW1kHXNUZQLdZ1gHX3ROw=="], 51 | 52 | "@azure/core-util": ["@azure/core-util@1.13.0", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-o0psW8QWQ58fq3i24Q1K2XfS/jYTxr7O1HRcyUE9bV9NttLU+kYOH82Ixj8DGlMTOWgxm1Sss2QAfKK5UkSPxw=="], 53 | 54 | "@azure/logger": ["@azure/logger@1.3.0", "", { "dependencies": { "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA=="], 55 | 56 | "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="], 57 | 58 | "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], 59 | 60 | "@eslint/config-array": ["@eslint/config-array@0.21.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ=="], 61 | 62 | "@eslint/config-helpers": ["@eslint/config-helpers@0.3.1", "", {}, "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA=="], 63 | 64 | "@eslint/core": ["@eslint/core@0.15.2", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg=="], 65 | 66 | "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], 67 | 68 | "@eslint/js": ["@eslint/js@9.33.0", "", {}, "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A=="], 69 | 70 | "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="], 71 | 72 | "@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.5", "", { "dependencies": { "@eslint/core": "^0.15.2", "levn": "^0.4.1" } }, "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w=="], 73 | 74 | "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], 75 | 76 | "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], 77 | 78 | "@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="], 79 | 80 | "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], 81 | 82 | "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], 83 | 84 | "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], 85 | 86 | "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], 87 | 88 | "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], 89 | 90 | "@octokit/auth-token": ["@octokit/auth-token@4.0.0", "", {}, "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA=="], 91 | 92 | "@octokit/core": ["@octokit/core@5.2.2", "", { "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg=="], 93 | 94 | "@octokit/endpoint": ["@octokit/endpoint@9.0.6", "", { "dependencies": { "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw=="], 95 | 96 | "@octokit/graphql": ["@octokit/graphql@7.1.1", "", { "dependencies": { "@octokit/request": "^8.4.1", "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g=="], 97 | 98 | "@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], 99 | 100 | "@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@9.2.2", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ=="], 101 | 102 | "@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@10.4.1", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg=="], 103 | 104 | "@octokit/request": ["@octokit/request@8.4.1", "", { "dependencies": { "@octokit/endpoint": "^9.0.6", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw=="], 105 | 106 | "@octokit/request-error": ["@octokit/request-error@5.1.1", "", { "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g=="], 107 | 108 | "@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], 109 | 110 | "@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="], 111 | 112 | "@types/bun": ["@types/bun@1.2.20", "", { "dependencies": { "bun-types": "1.2.20" } }, "sha512-dX3RGzQ8+KgmMw7CsW4xT5ITBSCrSbfHc36SNT31EOUg/LA9JWq0VDdEXDRSe1InVWpd2yLUM1FUF/kEOyTzYA=="], 113 | 114 | "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], 115 | 116 | "@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="], 117 | 118 | "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], 119 | 120 | "@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="], 121 | 122 | "@types/react": ["@types/react@19.1.10", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg=="], 123 | 124 | "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.39.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.39.1", "@typescript-eslint/type-utils": "8.39.1", "@typescript-eslint/utils": "8.39.1", "@typescript-eslint/visitor-keys": "8.39.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.39.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g=="], 125 | 126 | "@typescript-eslint/parser": ["@typescript-eslint/parser@8.39.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.39.1", "@typescript-eslint/types": "8.39.1", "@typescript-eslint/typescript-estree": "8.39.1", "@typescript-eslint/visitor-keys": "8.39.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg=="], 127 | 128 | "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.39.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.39.1", "@typescript-eslint/types": "^8.39.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-8fZxek3ONTwBu9ptw5nCKqZOSkXshZB7uAxuFF0J/wTMkKydjXCzqqga7MlFMpHi9DoG4BadhmTkITBcg8Aybw=="], 129 | 130 | "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.39.1", "", { "dependencies": { "@typescript-eslint/types": "8.39.1", "@typescript-eslint/visitor-keys": "8.39.1" } }, "sha512-RkBKGBrjgskFGWuyUGz/EtD8AF/GW49S21J8dvMzpJitOF1slLEbbHnNEtAHtnDAnx8qDEdRrULRnWVx27wGBw=="], 131 | 132 | "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.39.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ePUPGVtTMR8XMU2Hee8kD0Pu4NDE1CN9Q1sxGSGd/mbOtGZDM7pnhXNJnzW63zk/q+Z54zVzj44HtwXln5CvHA=="], 133 | 134 | "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.39.1", "", { "dependencies": { "@typescript-eslint/types": "8.39.1", "@typescript-eslint/typescript-estree": "8.39.1", "@typescript-eslint/utils": "8.39.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-gu9/ahyatyAdQbKeHnhT4R+y3YLtqqHyvkfDxaBYk97EcbfChSJXyaJnIL3ygUv7OuZatePHmQvuH5ru0lnVeA=="], 135 | 136 | "@typescript-eslint/types": ["@typescript-eslint/types@8.39.1", "", {}, "sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw=="], 137 | 138 | "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.39.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.39.1", "@typescript-eslint/tsconfig-utils": "8.39.1", "@typescript-eslint/types": "8.39.1", "@typescript-eslint/visitor-keys": "8.39.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-EKkpcPuIux48dddVDXyQBlKdeTPMmALqBUbEk38McWv0qVEZwOpVJBi7ugK5qVNgeuYjGNQxrrnoM/5+TI/BPw=="], 139 | 140 | "@typescript-eslint/utils": ["@typescript-eslint/utils@8.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.39.1", "@typescript-eslint/types": "8.39.1", "@typescript-eslint/typescript-estree": "8.39.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-VF5tZ2XnUSTuiqZFXCZfZs1cgkdd3O/sSYmdo2EpSyDlC86UM/8YytTmKnehOW3TGAlivqTDT6bS87B/GQ/jyg=="], 141 | 142 | "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.39.1", "", { "dependencies": { "@typescript-eslint/types": "8.39.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-W8FQi6kEh2e8zVhQ0eeRnxdvIoOkAp/CPAahcNio6nO9dsIwb9b34z90KOlheoyuVf6LSOEdjlkxSkapNEc+4A=="], 143 | 144 | "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.0", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-sOx1PKSuFwnIl7z4RN0Ls7N9AQawmR9r66eI5rFCzLDIs8HTIYrIpH9QjYWoX0lkgGrkLxXhi4QnK7MizPRrIg=="], 145 | 146 | "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], 147 | 148 | "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], 149 | 150 | "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], 151 | 152 | "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], 153 | 154 | "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 155 | 156 | "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], 157 | 158 | "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], 159 | 160 | "before-after-hook": ["before-after-hook@2.2.3", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="], 161 | 162 | "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], 163 | 164 | "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], 165 | 166 | "bun-types": ["bun-types@1.2.20", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-pxTnQYOrKvdOwyiyd/7sMt9yFOenN004Y6O4lCcCUoKVej48FS5cvTw9geRaEcB9TsDZaJKAxPTVvi8tFsVuXA=="], 167 | 168 | "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], 169 | 170 | "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], 171 | 172 | "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 173 | 174 | "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 175 | 176 | "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], 177 | 178 | "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], 179 | 180 | "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], 181 | 182 | "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], 183 | 184 | "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], 185 | 186 | "deprecation": ["deprecation@2.3.1", "", {}, "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="], 187 | 188 | "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], 189 | 190 | "eslint": ["eslint@9.33.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.1", "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.33.0", "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA=="], 191 | 192 | "eslint-config-prettier": ["eslint-config-prettier@9.1.2", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ=="], 193 | 194 | "eslint-plugin-prettier": ["eslint-plugin-prettier@5.5.4", "", { "dependencies": { "prettier-linter-helpers": "^1.0.0", "synckit": "^0.11.7" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "optionalPeers": ["@types/eslint", "eslint-config-prettier"] }, "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg=="], 195 | 196 | "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], 197 | 198 | "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], 199 | 200 | "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], 201 | 202 | "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], 203 | 204 | "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], 205 | 206 | "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], 207 | 208 | "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], 209 | 210 | "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], 211 | 212 | "fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="], 213 | 214 | "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], 215 | 216 | "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], 217 | 218 | "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], 219 | 220 | "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], 221 | 222 | "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], 223 | 224 | "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], 225 | 226 | "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], 227 | 228 | "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], 229 | 230 | "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], 231 | 232 | "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], 233 | 234 | "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], 235 | 236 | "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], 237 | 238 | "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], 239 | 240 | "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], 241 | 242 | "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], 243 | 244 | "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], 245 | 246 | "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], 247 | 248 | "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], 249 | 250 | "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], 251 | 252 | "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], 253 | 254 | "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], 255 | 256 | "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], 257 | 258 | "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], 259 | 260 | "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], 261 | 262 | "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], 263 | 264 | "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], 265 | 266 | "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], 267 | 268 | "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], 269 | 270 | "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], 271 | 272 | "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], 273 | 274 | "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], 275 | 276 | "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], 277 | 278 | "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], 279 | 280 | "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 281 | 282 | "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], 283 | 284 | "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], 285 | 286 | "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], 287 | 288 | "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], 289 | 290 | "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], 291 | 292 | "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], 293 | 294 | "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], 295 | 296 | "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], 297 | 298 | "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], 299 | 300 | "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], 301 | 302 | "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], 303 | 304 | "prettier-linter-helpers": ["prettier-linter-helpers@1.0.0", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w=="], 305 | 306 | "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], 307 | 308 | "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], 309 | 310 | "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], 311 | 312 | "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], 313 | 314 | "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], 315 | 316 | "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], 317 | 318 | "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], 319 | 320 | "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], 321 | 322 | "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], 323 | 324 | "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], 325 | 326 | "synckit": ["synckit@0.11.11", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw=="], 327 | 328 | "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], 329 | 330 | "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], 331 | 332 | "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 333 | 334 | "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], 335 | 336 | "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], 337 | 338 | "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], 339 | 340 | "typescript-eslint": ["typescript-eslint@8.39.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.39.1", "@typescript-eslint/parser": "8.39.1", "@typescript-eslint/typescript-estree": "8.39.1", "@typescript-eslint/utils": "8.39.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-GDUv6/NDYngUlNvwaHM1RamYftxf782IyEDbdj3SeaIHHv8fNQVRC++fITT7kUJV/5rIA/tkoRSSskt6osEfqg=="], 341 | 342 | "undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], 343 | 344 | "undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="], 345 | 346 | "universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="], 347 | 348 | "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], 349 | 350 | "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], 351 | 352 | "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], 353 | 354 | "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], 355 | 356 | "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], 357 | 358 | "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], 359 | 360 | "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], 361 | 362 | "@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="], 363 | 364 | "@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="], 365 | 366 | "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], 367 | 368 | "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], 369 | 370 | "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], 371 | 372 | "@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="], 373 | 374 | "@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="], 375 | 376 | "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], 377 | } 378 | } 379 | --------------------------------------------------------------------------------