Skip to content

Commit 1ff0394

Browse files
committed
initial commit
0 parents  commit 1ff0394

11 files changed

Lines changed: 1453 additions & 0 deletions

File tree

.github/workflows/agent.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Code Explainer Bot
2+
3+
on:
4+
issue_comment:
5+
types: [created]
6+
pull_request_review_comment:
7+
types: [created]
8+
9+
jobs:
10+
run-agent:
11+
if: |
12+
contains(github.event.comment.body, '@gitagent explain') ||
13+
contains(github.event.comment.body, '@gitagent test')
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
- name: Checkout agent repo
18+
uses: actions/checkout@v4
19+
20+
- name: Setup Node.js
21+
uses: actions/setup-node@v4
22+
with:
23+
node-version: "20"
24+
25+
- name: Install dependencies
26+
run: npm install
27+
28+
- name: Run agent
29+
env:
30+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31+
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
32+
run: |
33+
node -e "
34+
import('./index.js').then(m => m.run(${{ toJson(github.event) }}))
35+
"

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

README.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# Code Explainer Bot
2+
3+
A GitAgent that lives in your GitHub repo and responds to `@gitagent` commands
4+
in PR comments. Tag it on any file or function and it explains the code in plain
5+
English — or generates unit tests instantly.
6+
7+
Built for the [GitAgent Hackathon](https://hackculture.dev) by HackCulture.
8+
9+
---
10+
11+
## Demo
12+
13+
On any PR or issue comment, type:
14+
15+
```
16+
@gitagent explain src/auth/token.js#L20-L45
17+
```
18+
19+
The bot replies in-thread:
20+
21+
> ### What this code does
22+
>
23+
> This function validates a JWT token and extracts the user payload. It checks
24+
> the signature against the secret key and throws if the token is expired.
25+
>
26+
> **Key points:**
27+
> - Uses `jsonwebtoken` under the hood — not custom crypto
28+
> - The `ignoreExpiration` flag is intentionally false here
29+
> - Returns a plain JS object, not a class instance
30+
>
31+
> **Watch out for:** If `JWT_SECRET` is undefined in env, this will throw a
32+
> confusing `secretOrPrivateKey` error rather than a missing env error.
33+
34+
Or generate tests:
35+
36+
```
37+
@gitagent test src/utils/format.js
38+
```
39+
40+
---
41+
42+
## Setup (5 minutes)
43+
44+
### 1. Add this repo as a GitHub Actions dependency
45+
46+
Copy `.github/workflows/agent.yml` into your target repository.
47+
48+
### 2. Add secrets
49+
50+
In your repo → Settings → Secrets → Actions, add:
51+
52+
| Secret | Where to get it |
53+
|---|---|
54+
| `GEMINI_API_KEY` | [Google AI Studio](https://aistudio.google.com) — free tier, no card needed |
55+
| `GITHUB_TOKEN` | Auto-provided by GitHub Actions — nothing to do |
56+
57+
### 3. Install the agent (optional local use)
58+
59+
```bash
60+
npm install -g @open-gitagent/gitagent
61+
gitagent validate # confirm agent.yaml is valid
62+
gitagent info # see agent summary
63+
```
64+
65+
### 4. Try it
66+
67+
Open a PR, drop a comment with `@gitagent explain <filepath>`, and watch the bot reply.
68+
69+
---
70+
71+
## Commands
72+
73+
| Command | Example | What it does |
74+
|---|---|---|
75+
| `@gitagent explain <file>` | `@gitagent explain src/api.js` | Explains the whole file |
76+
| `@gitagent explain <file>#L<n>-L<m>` | `@gitagent explain src/api.js#L10-L30` | Explains a specific line range |
77+
| `@gitagent test <file>` | `@gitagent test src/utils.js` | Generates unit tests for the file |
78+
79+
---
80+
81+
## Architecture
82+
83+
```
84+
code-explainer-bot/
85+
├── agent.yaml # gitagent manifest (model, skills, triggers)
86+
├── SOUL.md # agent identity and communication style
87+
├── RULES.md # hard constraints
88+
├── index.js # runtime: parses comments, calls Gemini, posts replies
89+
├── skills/
90+
│ ├── explain-code/ # explain skill definition
91+
│ └── generate-tests/ # test generation skill definition
92+
└── .github/workflows/
93+
└── agent.yml # GitHub Actions trigger
94+
```
95+
96+
The agent is framework-agnostic — export to any supported adapter:
97+
98+
```bash
99+
gitagent export --format system-prompt # plain system prompt
100+
gitagent export --format openai # OpenAI Agents SDK
101+
gitagent export --format claude-code # Claude Code
102+
gitagent run . --adapter lyzr # Lyzr Studio
103+
```
104+
105+
---
106+
107+
## Why this matters
108+
109+
- **Onboarding**: new engineers understand unfamiliar code in seconds instead of hours
110+
- **Code review**: reviewers can tag confusing sections and get instant context
111+
- **Test coverage**: generate test stubs for any file without leaving the PR
112+
113+
---
114+
115+
## Free tier usage
116+
117+
This agent runs entirely on free credits:
118+
- **Gemini 2.0 Flash**: 1M tokens/day free via Google AI Studio
119+
- **GitHub Actions**: 2,000 free minutes/month on public repos
120+
- **GitHub API**: 5,000 requests/hour with a standard token
121+
122+
Zero cost to run for a typical engineering team.
123+
124+
---
125+
126+
## License
127+
128+
MIT

RULES.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# RULES.md — Hard Constraints
2+
3+
## Must always
4+
- Reply only in the GitHub comment thread where you were mentioned
5+
- Include the filename and line range you are explaining
6+
- Label generated tests with the framework you used (Jest, pytest, etc.)
7+
- Respect the language of the file being explained
8+
9+
## Must never
10+
- Suggest or make changes to production code
11+
- Store or log any code content outside the GitHub API response
12+
- Reply to comments that don't contain @gitagent explain or @gitagent test
13+
- Hallucinate function behavior — if uncertain, say so explicitly
14+
15+
## Scope limits
16+
- Only read files from the repository where the comment was made
17+
- Do not access external URLs or third-party APIs beyond GitHub and the LLM

SOUL.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# SOUL.md — Code Explainer Bot
2+
3+
## Identity
4+
You are a senior engineer and mentor. You have deep expertise in software
5+
engineering but your superpower is explaining complex things simply. You write
6+
the way a great tech lead talks to a junior — clear, warm, never condescending.
7+
8+
## Purpose
9+
You exist to make code reviews faster and onboarding easier. When a developer
10+
tags you on a PR or file, you explain exactly what the code does and why it
11+
matters. When asked for tests, you write clean, minimal, runnable unit tests.
12+
13+
## Communication style
14+
- Plain English first. Avoid jargon unless the audience clearly knows it.
15+
- Use short paragraphs. No walls of text.
16+
- Bullet points for steps or lists, prose for explanations.
17+
- Always end with one actionable next step or insight if relevant.
18+
- Never say "Great question!" or add filler phrases.
19+
20+
## Values
21+
- Accuracy over speed — never guess if you're not sure.
22+
- Respect the developer's time — be concise.
23+
- Make the codebase less scary for newcomers.

agent.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
spec_version: "0.1.0"
2+
name: code-explainer-bot
3+
version: 0.1.0
4+
description: >
5+
A GitAgent that watches for @gitagent explain and @gitagent test comments
6+
on GitHub PRs and issues. Replies with plain-English explanations or
7+
auto-generated unit tests — directly in the thread.
8+
9+
author: hkv-31
10+
license: MIT
11+
12+
model:
13+
preferred: gemini-2.0-flash
14+
fallback:
15+
- gemini-1.5-flash
16+
constraints:
17+
temperature: 0.3
18+
max_tokens: 2048
19+
20+
skills:
21+
- explain-code
22+
- generate-tests
23+
24+
tags:
25+
- developer-tools
26+
- code-review
27+
- onboarding
28+
- testing

index.js

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { Octokit } from "@octokit/rest";
2+
3+
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
4+
5+
const EXPLAIN_TRIGGER = "@gitagent explain";
6+
const TEST_TRIGGER = "@gitagent test";
7+
8+
async function fetchFileContent(owner, repo, filePath, ref) {
9+
try {
10+
const { data } = await octokit.repos.getContent({ owner, repo, path: filePath, ref });
11+
return Buffer.from(data.content, "base64").toString("utf-8");
12+
} catch {
13+
return null;
14+
}
15+
}
16+
17+
function parseComment(body) {
18+
const lineMatch = body.match(/([^\s#]+)(?:#L(\d+)(?:-L?(\d+))?)?/);
19+
const filePath = lineMatch?.[1]?.replace(/@gitagent\s+(explain|test)\s*/i, "").trim() || null;
20+
const startLine = lineMatch?.[2] ? parseInt(lineMatch[2]) : null;
21+
const endLine = lineMatch?.[3] ? parseInt(lineMatch[3]) : startLine;
22+
return { filePath, startLine, endLine };
23+
}
24+
25+
function sliceLines(content, start, end) {
26+
if (!start) return content;
27+
return content.split("\n").slice(start - 1, end).join("\n");
28+
}
29+
30+
async function callLLM(prompt) {
31+
const res = await fetch("https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=" + process.env.GEMINI_API_KEY, {
32+
method: "POST",
33+
headers: { "Content-Type": "application/json" },
34+
body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] }),
35+
});
36+
const data = await res.json();
37+
return data.candidates?.[0]?.content?.parts?.[0]?.text || "Sorry, I couldn't generate a response.";
38+
}
39+
40+
async function postComment(owner, repo, issueNumber, body) {
41+
await octokit.issues.createComment({ owner, repo, issue_number: issueNumber, body });
42+
}
43+
44+
async function handleExplain({ owner, repo, issueNumber, commentBody, prRef }) {
45+
const { filePath, startLine, endLine } = parseComment(commentBody);
46+
if (!filePath) {
47+
return postComment(owner, repo, issueNumber, "Please specify a file path, e.g. `@gitagent explain src/auth.js#L10-L30`");
48+
}
49+
50+
const raw = await fetchFileContent(owner, repo, filePath, prRef);
51+
if (!raw) {
52+
return postComment(owner, repo, issueNumber, `Could not read \`${filePath}\`. Make sure the path is correct.`);
53+
}
54+
55+
const snippet = sliceLines(raw, startLine, endLine);
56+
const lineInfo = startLine ? ` (lines ${startLine}${endLine || startLine})` : "";
57+
58+
const prompt = `You are a senior engineer explaining code to a teammate.
59+
Explain what the following code does in plain English. Be concise.
60+
Format your response exactly like this:
61+
62+
### What this code does
63+
64+
[2-4 sentence summary]
65+
66+
**Key points:**
67+
- [point]
68+
- [point]
69+
70+
**Watch out for:** [one gotcha or edge case if relevant, otherwise omit this line]
71+
72+
File: ${filePath}${lineInfo}
73+
74+
\`\`\`
75+
${snippet}
76+
\`\`\``;
77+
78+
const explanation = await callLLM(prompt);
79+
await postComment(owner, repo, issueNumber, explanation);
80+
}
81+
82+
async function handleTest({ owner, repo, issueNumber, commentBody, prRef }) {
83+
const { filePath } = parseComment(commentBody);
84+
if (!filePath) {
85+
return postComment(owner, repo, issueNumber, "Please specify a file path, e.g. `@gitagent test src/utils.js`");
86+
}
87+
88+
const raw = await fetchFileContent(owner, repo, filePath, prRef);
89+
if (!raw) {
90+
return postComment(owner, repo, issueNumber, `Could not read \`${filePath}\`. Make sure the path is correct.`);
91+
}
92+
93+
const ext = filePath.split(".").pop();
94+
const lang = { js: "JavaScript/Jest", ts: "TypeScript/Jest", py: "Python/pytest", rb: "Ruby/RSpec" }[ext] || ext;
95+
96+
const prompt = `You are a senior engineer writing unit tests.
97+
Generate minimal, runnable unit tests for the code below.
98+
Use ${lang}. Cover: happy path, edge cases, and error cases.
99+
Format your response exactly like this:
100+
101+
### Generated tests for \`${filePath}\`
102+
103+
Framework: ${lang}
104+
105+
\`\`\`${ext}
106+
[test code]
107+
\`\`\`
108+
109+
> These are stubs — add mocks for any external dependencies.
110+
111+
\`\`\`
112+
${raw}
113+
\`\`\``;
114+
115+
const tests = await callLLM(prompt);
116+
await postComment(owner, repo, issueNumber, tests);
117+
}
118+
119+
// --- Main entry point (called by GitHub Actions webhook) ---
120+
export async function run(payload) {
121+
const body = payload.comment?.body || "";
122+
const owner = payload.repository.owner.login;
123+
const repo = payload.repository.name;
124+
const issueNumber = payload.issue?.number || payload.pull_request?.number;
125+
const prRef = payload.pull_request?.head?.sha || payload.repository.default_branch;
126+
127+
if (body.includes(EXPLAIN_TRIGGER)) {
128+
await handleExplain({ owner, repo, issueNumber, commentBody: body, prRef });
129+
} else if (body.includes(TEST_TRIGGER)) {
130+
await handleTest({ owner, repo, issueNumber, commentBody: body, prRef });
131+
}
132+
}

0 commit comments

Comments
 (0)