Skip to content
Open
Show file tree
Hide file tree
Changes from all 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"
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

# 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
178 changes: 178 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,178 @@
"use server";

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

/**
* Step 1: Analyze the issue and generate a fix (NO PR creation).
* Returns analysis, fix data, and PR metadata from the Lamatic flow.
*/
export async function handleFixIssue(input: {
issue_url: string;
file_path?: string;
file_content?: string;
}) {
try {
console.log("[agent] Input received", {
hasIssueUrl: Boolean(input.issue_url),
hasFilePath: Boolean(input.file_path),
hasFileContent: Boolean(input.file_content),
});
const flowId = process.env.GITHUB_AUTO_FIX;
if (!flowId) throw new Error("Missing flow ID");

// 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 received", {
hasAnalysis: Boolean(result?.analysis),
hasFix: Boolean(result?.fix),
hasPr: Boolean(result?.pr),
});

return {
success: true,
analysis,
fix,
pr, // branch_name, commit_message, pr_title, pr_body
};
} catch (error) {
console.error("[agent] Error:", error);

return {
success: false,
error: error instanceof Error ? error.message : "Unknown error",
};
}
}

/**
* Step 2: Create a GitHub PR using the fix data from Step 1.
* This is called separately only when the user clicks "Create PR".
*/
export async function handleCreatePR(input: {
issue_url: string;
file_path: string;
fix: { updated_code: string };
pr: {
branch_name: string;
commit_message: string;
pr_title: string;
pr_body: string;
};
}) {
try {
const { issue_url, file_path, fix, pr } = input;

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

const [, owner, repo] = match;

// 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());

const baseBranch = repoData.default_branch;

// 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;

// 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,
}),
});

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

const fileSha = fileData.sha;

// Update file on branch
await fetch(
`https://api.github.com/repos/${owner}/${repo}/contents/${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,
}),
},
);

// 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,
};
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] PR creation error:", error);

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

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

if (!body.issue_url || !body.file_path || !body.fix || !body.pr) {
return Response.json(
{
success: false,
error:
"issue_url, file_path, fix, and pr are all required",
},
{ status: 400 },
);
}

const result = await handleCreatePR(body);

return Response.json(result);
} catch (error) {
console.error("[API ERROR]", error);

return Response.json(
{
success: false,
error: "Internal server error",
},
{ status: 500 },
);
}
}
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.
Loading
Loading