Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
palagdan committed Dec 10, 2024
1 parent 9c4b14a commit 2b9f8e7
Show file tree
Hide file tree
Showing 24 changed files with 1,053 additions and 1 deletion.
17 changes: 17 additions & 0 deletions .github/workflows/AddLabelToIssueTest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Add Label To Issue Action Test

on:
push:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Run the custom action
uses: ./
with:
mode: ISSUE_LABEL
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

*.env
45 changes: 44 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,45 @@
# GitMate
Conversational AI for GitHub Workflows This project integrates a conversational AI assistant to streamline software development workflows for the Knowledge-based and Software Systems Group (KBSS). The AI automates tasks like issue tracking, pull request management, and code reviews within the GitHub framework.

GitMate is a conversational AI assistant designed to optimize and streamline GitHub workflows. This project integrates a sophisticated AI bot that automates common tasks within the GitHub ecosystem, enhancing the productivity of the Knowledge-based and Software Systems Group (KBSS). With GitMate, developers can save time and reduce manual effort by automating key GitHub tasks such as issue tracking, pull request management, and code reviews.

## Core Features

---

### 1. **Issue Management**

- **Automated Task Management**: GitMate automates the labeling of new issues based on content analysis. It detects keywords such as "bug" or "enhancement" in the issue description and assigns the corresponding labels automatically, ensuring that issues are categorized properly from the outset.

Example: If a user reports a problem with the system's functionality, GitMate could automatically label the issue as "bug".

### 2. **Pull Request Management**

- **Automated Review Assistance**: GitMate can assist in automating certain aspects of pull request (PR) management, including providing reviews, identifying merge conflicts, and suggesting changes to improve code quality.

### 3. **Code Review Automation**

- **AI-Driven Code Review**: The bot can provide initial feedback on pull requests by analyzing code changes, highlighting potential issues, and suggesting improvements, helping to expedite the review process.

---

## Usage

### Adding Labels to Issues Automatically

To enable GitMate to automatically label issues when they are opened or edited, use the following GitHub Actions workflow configuration.

```yaml
on:
issues:
types: [opened, edited]

jobs:
add-label-to-issue:
runs-on: ubuntu-latest
name: Add label to created issue
steps:
- uses: palagdan/GitMate@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
mode: 'issue label'
12 changes: 12 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: 'GitMate'
description: 'GitMate is your buddy in a development world'

inputs:
mode:
description: 'Mode for the bot'
required: true
default: 'mirror'

runs:
using: 'node20'
main: 'dist/index.ts'
101 changes: 101 additions & 0 deletions dist/actions/addLabelsToIssueAction/addLabelsToIssueAction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core"));
const github = __importStar(require("@actions/github"));
const openai_1 = require("openai");
const utils_1 = require("@/utils");
const addLabelToIssueAction = () => __awaiter(void 0, void 0, void 0, function* () {
var _a, _b, _c;
try {
const apiKey = core.getInput("openai-api-key");
const githubToken = core.getInput("github-token");
const octokit = github.getOctokit(githubToken);
const issue = yield octokit.rest.issues.get(Object.assign(Object.assign({}, github.context.issue), { issue_number: github.context.issue.number }));
const availableLabels = yield octokit.rest.issues.listLabelsForRepo(Object.assign({}, github.context.repo));
const prompt = createAddLabelsToIssuePrompt(issue.data.title, issue.data.body || '', availableLabels.data.map(label => label.name), './promptTemplate.txt');
core.debug(`Prompt: ${prompt}`);
const client = new openai_1.OpenAI({ apiKey });
const params = {
messages: [{ role: 'user', content: prompt }],
model: 'gpt-4',
};
const chatCompletion = yield client.chat.completions.create(params);
core.debug(`Response: ${JSON.stringify(chatCompletion)}`);
const responseText = ((_c = (_b = (_a = chatCompletion.choices[0]) === null || _a === void 0 ? void 0 : _a.message) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.trim()) || '';
const parsedResponse = JSON.parse(responseText);
const labels = parsedResponse.labels;
if (labels.length > 0) {
yield octokit.rest.issues.setLabels({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
issue_number: github.context.issue.number,
labels,
});
core.info(`Labels added: ${labels.join(", ")}`);
}
else {
core.info('No labels suggested to add.');
}
}
catch (error) {
core.setFailed(`Error occurred: ${error.message}`);
}
});
/**
* Create a prompt for OpenAI based on the issue details and available labels.
* @param issueTitle - Title of the GitHub issue.
* @param issueBody - Body of the GitHub issue.
* @param availableLabels - List of available labels in the repository.
* @param templateFilePath - Path to the prompt template file.
* @returns The generated prompt string.
*/
const createAddLabelsToIssuePrompt = (issueTitle, issueBody, availableLabels, templateFilePath) => {
const template = (0, utils_1.loadFile)(templateFilePath);
return template
.replace('{{issueTitle}}', issueTitle)
.replace('{{issueBody}}', issueBody)
.replace('{{availableLabels}}', availableLabels.join(", "));
};
exports.default = addLabelToIssueAction;
7 changes: 7 additions & 0 deletions dist/actions/addLabelsToIssueAction/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const addLabelsToIssueAction_1 = __importDefault(require("./addLabelsToIssueAction"));
exports.default = addLabelsToIssueAction_1.default;
8 changes: 8 additions & 0 deletions dist/actions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.addLabelsToIssueAction = void 0;
const addLabelsToIssueAction_1 = __importDefault(require("./addLabelsToIssueAction"));
exports.addLabelsToIssueAction = addLabelsToIssueAction_1.default;
5 changes: 5 additions & 0 deletions dist/constants/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ISSUE_LABEL = void 0;
const modes_js_1 = require("./modes.js");
Object.defineProperty(exports, "ISSUE_LABEL", { enumerable: true, get: function () { return modes_js_1.ISSUE_LABEL; } });
4 changes: 4 additions & 0 deletions dist/constants/modes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ISSUE_LABEL = void 0;
exports.ISSUE_LABEL = "issue label";
71 changes: 71 additions & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core"));
const constants_1 = require("./constants");
const actions_1 = require("./actions");
function run() {
return __awaiter(this, void 0, void 0, function* () {
try {
const mode = core.getInput('mode');
switch (mode) {
case constants_1.ISSUE_LABEL:
yield (0, actions_1.addLabelsToIssueAction)();
break;
default:
core.setFailed('Mode is not supported');
break;
}
}
catch (error) {
if (error instanceof Error) {
core.setFailed(error.message);
}
else {
core.setFailed('An unknown error occurred');
}
}
});
}
run();
46 changes: 46 additions & 0 deletions dist/utils/fsUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadFile = void 0;
const fs = __importStar(require("node:fs"));
/**
* Load a file.
* @param filePath - Path to the file to be loaded.
* @returns File content as a string.
*/
const loadFile = (filePath) => {
return fs.readFileSync(filePath, 'utf-8');
};
exports.loadFile = loadFile;
5 changes: 5 additions & 0 deletions dist/utils/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadFile = void 0;
const fsUtils_1 = require("./fsUtils");
Object.defineProperty(exports, "loadFile", { enumerable: true, get: function () { return fsUtils_1.loadFile; } });
Loading

0 comments on commit 2b9f8e7

Please sign in to comment.