Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions kits/agentic/github-auto-fix-agent/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
GITHUB_AUTO_FIX = "YOUR_FLOW_ID"
LAMATIC_API_URL = "YOUR_API_ENDPOINT"
LAMATIC_API_KEY = "YOUR_API_KEY"
LAMATIC_PROJECT_ID = "YOUR_PROJECT_ID"
GITHUB_TOKEN = "YOUR_GITHUB_TOKEN"
Comment thread
RitoG09 marked this conversation as resolved.
Outdated
41 changes: 41 additions & 0 deletions kits/agentic/github-auto-fix-agent/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env

Comment thread
RitoG09 marked this conversation as resolved.
# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
55 changes: 55 additions & 0 deletions kits/agentic/github-auto-fix-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# GitHub Auto Fix Agent

An AI-powered agent that analyzes GitHub issues and generates code fixes with ready-to-create pull requests.

---

## Features

- Understands GitHub issues automatically
- Identifies root cause of bugs
- Generates minimal code fixes (diff-based)
- Prepares PR metadata (title, body, branch)
- One-click PR creation

---

## How It Works

1. User provides a GitHub issue URL
2. Lamatic flow analyzes the issue
3. AI generates:
- Issue summary
- Root cause
- Code fix (diff)
- PR metadata
4. User reviews and creates PR

---

## Flow Overview

```mermaid
flowchart TD
A[User Input] --> B[Frontend]
B --> C[Backend API]
C --> D[Lamatic Flow]
D --> E[Analysis + Fix + PR Data]
E --> F[Frontend Preview]
F --> G[User Creates PR]
G --> H[GitHub PR Created]
```

## Lamatic Workflow

<p align="center">
<img src="./public/flows.png" alt="Lamatic Workflow" width="700"/>
</p>

## Setup Locally

```bash
cd kits/agentic/github-auto-fix-agent
npm install
cp .env.example .env
npm run dev
Comment thread
RitoG09 marked this conversation as resolved.
143 changes: 143 additions & 0 deletions kits/agentic/github-auto-fix-agent/actions/orchestrate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
"use server";

import { lamaticClient } from "@/lib/lamatic-client";

export async function handleFixIssue(input: {
issue_url: string;
file_path?: string;
file_content?: string;
}) {
try {
console.log("[agent] Input:", input);

const flowId = process.env.GITHUB_AUTO_FIX;
console.log("FLOW ID:", flowId);
if (!flowId) throw new Error("Missing flow ID");

Comment thread
RitoG09 marked this conversation as resolved.
// 🔥 Step 1: Run Lamatic Flow
const resData = await lamaticClient.executeFlow(flowId, input);

if (resData.status !== "success" || !resData.result) {
throw new Error(resData.message || "Lamatic flow failed");
}

const result = resData.result;

const { analysis, fix, pr } = result;

console.log("[agent] Flow output:", result);

// 🔥 Step 2: Extract repo info
const match = input.issue_url.match(
/github.com\/(.*?)\/(.*?)\/issues\/(\d+)/,
);
if (!match) throw new Error("Invalid GitHub issue URL");

const [, owner, repo] = match;

// 🔥 Step 3: Get repo default branch
const repoData = await fetch(
`https://api.github.com/repos/${owner}/${repo}`,
{
headers: {
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
},
},
).then((res) => res.json());
Comment thread
RitoG09 marked this conversation as resolved.

const baseBranch = repoData.default_branch;

// 🔥 Step 4: Get latest commit SHA
const refData = await fetch(
`https://api.github.com/repos/${owner}/${repo}/git/ref/heads/${baseBranch}`,
{
headers: {
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
},
},
).then((res) => res.json());

const baseSha = refData.object.sha;

// 🔥 Step 5: Create branch
await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
ref: `refs/heads/${pr.branch_name}`,
sha: baseSha,
}),
});
Comment thread
RitoG09 marked this conversation as resolved.

// 🔥 Step 6: Get file SHA
if (!input.file_path) {
throw new Error("file_path is required for PR creation");
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

const fileData = await fetch(
`https://api.github.com/repos/${owner}/${repo}/contents/${input.file_path}`,
{
headers: {
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
},
},
).then((res) => res.json());

const fileSha = fileData.sha;

// 🔥 Step 7: Update file
await fetch(
`https://api.github.com/repos/${owner}/${repo}/contents/${input.file_path}`,
{
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
message: pr.commit_message,
content: Buffer.from(fix.updated_code).toString("base64"),
branch: pr.branch_name,
sha: fileSha,
}),
},
);

// 🔥 Step 8: Create PR
const prRes = await fetch(
`https://api.github.com/repos/${owner}/${repo}/pulls`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
title: pr.pr_title,
head: pr.branch_name,
base: baseBranch,
body: pr.pr_body,
}),
},
);

const prData = await prRes.json();

return {
success: true,
pr_url: prData.html_url,
analysis,
fix,
};
Comment on lines +164 to +169
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

PR creation response not validated before returning URL.

prData.html_url is returned without verifying the PR was actually created. If PR creation fails (e.g., duplicate PR, permission issues), html_url will be undefined and the success response will be misleading.

🐛 Proposed fix to validate PR creation
     const prData = await prRes.json();

+    if (!prRes.ok || !prData.html_url) {
+      throw new Error(`Failed to create PR: ${prData.message || "Unknown error"}`);
+    }
+
     return {
       success: true,
       pr_url: prData.html_url,
       analysis,
       fix,
     };
📝 Committable suggestion

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

Suggested change
const prData = await prRes.json();
return {
success: true,
pr_url: prData.html_url,
analysis,
fix,
};
const prData = await prRes.json();
if (!prRes.ok || !prData.html_url) {
throw new Error(`Failed to create PR: ${prData.message || "Unknown error"}`);
}
return {
success: true,
pr_url: prData.html_url,
analysis,
fix,
};

} catch (error) {
console.error("[agent] Error:", error);

return {
success: false,
error: error instanceof Error ? error.message : "Unknown error",
};
}
}
28 changes: 28 additions & 0 deletions kits/agentic/github-auto-fix-agent/app/api/fix/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { handleFixIssue } from "@/actions/orchestrate";

export async function POST(req: Request) {
try {
const body = await req.json();

if (!body.issue_url) {
return Response.json(
{ success: false, error: "issue_url is required" },
{ status: 400 },
);
}

const result = await handleFixIssue(body);

return Response.json(result);
Comment on lines +3 to +16
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Protect this endpoint with authentication and repository scope checks.

POST /api/fix is currently unauthenticated, but it can trigger privileged actions (issue analysis + PR creation). This allows arbitrary external callers to consume your token-backed automation.

🔐 Proposed hardening
 export async function POST(req: Request) {
   try {
+    const internalApiToken = process.env.INTERNAL_API_TOKEN;
+    const authHeader = req.headers.get("authorization");
+    if (!internalApiToken || authHeader !== `Bearer ${internalApiToken}`) {
+      return Response.json(
+        { success: false, error: "Unauthorized" },
+        { status: 401 },
+      );
+    }
+
     const body = await req.json();
 
-    if (!body.issue_url) {
+    if (typeof body.issue_url !== "string" || body.issue_url.trim() === "") {
       return Response.json(
         { success: false, error: "issue_url is required" },
         { status: 400 },
       );
     }
+
+    let parsedIssueUrl: URL;
+    try {
+      parsedIssueUrl = new URL(body.issue_url);
+    } catch {
+      return Response.json(
+        { success: false, error: "issue_url must be a valid URL" },
+        { status: 400 },
+      );
+    }
+    if (parsedIssueUrl.hostname !== "github.com") {
+      return Response.json(
+        { success: false, error: "Only github.com issue URLs are supported" },
+        { status: 400 },
+      );
+    }
 
     const result = await handleFixIssue(body);
📝 Committable suggestion

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

Suggested change
export async function POST(req: Request) {
try {
const body = await req.json();
if (!body.issue_url) {
return Response.json(
{ success: false, error: "issue_url is required" },
{ status: 400 },
);
}
const result = await handleFixIssue(body);
return Response.json(result);
export async function POST(req: Request) {
try {
const internalApiToken = process.env.INTERNAL_API_TOKEN;
const authHeader = req.headers.get("authorization");
if (!internalApiToken || authHeader !== `Bearer ${internalApiToken}`) {
return Response.json(
{ success: false, error: "Unauthorized" },
{ status: 401 },
);
}
const body = await req.json();
if (typeof body.issue_url !== "string" || body.issue_url.trim() === "") {
return Response.json(
{ success: false, error: "issue_url is required" },
{ status: 400 },
);
}
let parsedIssueUrl: URL;
try {
parsedIssueUrl = new URL(body.issue_url);
} catch {
return Response.json(
{ success: false, error: "issue_url must be a valid URL" },
{ status: 400 },
);
}
if (parsedIssueUrl.hostname !== "github.com") {
return Response.json(
{ success: false, error: "Only github.com issue URLs are supported" },
{ status: 400 },
);
}
const result = await handleFixIssue(body);
return Response.json(result);

} catch (error) {
console.error("[API ERROR]", error);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid logging raw error objects from privileged flows.

Raw exceptions may include sensitive context from upstream systems. Log a request ID + sanitized metadata instead.

🛡️ Proposed safe logging pattern
   } catch (error) {
-    console.error("[API ERROR]", error);
+    const requestId = crypto.randomUUID();
+    console.error("[API ERROR]", {
+      requestId,
+      name: error instanceof Error ? error.name : "UnknownError",
+    });
 
     return Response.json(
       {
         success: false,
-        error: "Internal server error",
+        error: `Internal server error (${requestId})`,
       },
       { status: 500 },
     );
   }


return Response.json(
{
success: false,
error: "Internal server error",
},
{ status: 500 },
);
}
}
Binary file not shown.
84 changes: 84 additions & 0 deletions kits/agentic/github-auto-fix-agent/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
@import "tailwindcss";

:root {
--background: #FFFDF7;
--foreground: #1a1a1a;

/* Yellow Primary Palette */
--primary-50: #FFFEF5;
--primary-100: #FFFCE8;
--primary-200: #FFF8C5;
--primary-300: #FFF09E;
--primary-400: #FFE566;
--primary-500: #FACC15;
--primary-600: #EAB308;
--primary-700: #CA8A04;
--primary-800: #A16207;
--primary-900: #854D0E;

/* Neutrals */
--surface: #FFFFFF;
--surface-raised: #FFFEF9;
--border-light: #F3F0E8;
--border-medium: #E8E3D7;
--text-primary: #1C1917;
--text-secondary: #57534E;
--text-tertiary: #A8A29E;
}

@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-primary-50: var(--primary-50);
--color-primary-100: var(--primary-100);
--color-primary-200: var(--primary-200);
--color-primary-300: var(--primary-300);
--color-primary-400: var(--primary-400);
--color-primary-500: var(--primary-500);
--color-primary-600: var(--primary-600);
--color-primary-700: var(--primary-700);
--color-primary-800: var(--primary-800);
--color-primary-900: var(--primary-900);
--color-surface: var(--surface);
--color-surface-raised: var(--surface-raised);
--color-border-light: var(--border-light);
--color-border-medium: var(--border-medium);
--color-text-primary: var(--text-primary);
--color-text-secondary: var(--text-secondary);
--color-text-tertiary: var(--text-tertiary);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}

body {
background: var(--background);
color: var(--foreground);
font-family: var(--font-sans), Arial, Helvetica, sans-serif;
}

/* Custom scrollbar */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--primary-300);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--primary-400);
}

/* Selection */
::selection {
background: var(--primary-200);
color: var(--primary-900);
}

/* Smooth focus rings */
*:focus-visible {
outline: 2px solid var(--primary-500);
outline-offset: 2px;
}
Loading