fixing auto verify for kanban issues

This commit is contained in:
Test User
2025-12-22 12:10:54 -05:00
parent 9702f142c4
commit 9586589453
30 changed files with 1376 additions and 306 deletions

View File

@@ -0,0 +1,18 @@
/**
* GitHub routes - HTTP API for GitHub integration
*/
import { Router } from 'express';
import { createCheckGitHubRemoteHandler } from './routes/check-github-remote.js';
import { createListIssuesHandler } from './routes/list-issues.js';
import { createListPRsHandler } from './routes/list-prs.js';
export function createGitHubRoutes(): Router {
const router = Router();
router.post('/check-remote', createCheckGitHubRemoteHandler());
router.post('/issues', createListIssuesHandler());
router.post('/prs', createListPRsHandler());
return router;
}

View File

@@ -0,0 +1,71 @@
/**
* GET /check-github-remote endpoint - Check if project has a GitHub remote
*/
import type { Request, Response } from 'express';
import { execAsync, execEnv, getErrorMessage, logError } from './common.js';
export interface GitHubRemoteStatus {
hasGitHubRemote: boolean;
remoteUrl: string | null;
owner: string | null;
repo: string | null;
}
export async function checkGitHubRemote(projectPath: string): Promise<GitHubRemoteStatus> {
const status: GitHubRemoteStatus = {
hasGitHubRemote: false,
remoteUrl: null,
owner: null,
repo: null,
};
try {
// Get the remote URL (origin by default)
const { stdout } = await execAsync('git remote get-url origin', {
cwd: projectPath,
env: execEnv,
});
const remoteUrl = stdout.trim();
status.remoteUrl = remoteUrl;
// Check if it's a GitHub URL
// Formats: https://github.com/owner/repo.git, git@github.com:owner/repo.git
const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([^/]+)\/([^/.]+)/);
const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/([^/.]+)/);
const match = httpsMatch || sshMatch;
if (match) {
status.hasGitHubRemote = true;
status.owner = match[1];
status.repo = match[2].replace(/\.git$/, '');
}
} catch {
// No remote or not a git repo - that's okay
}
return status;
}
export function createCheckGitHubRemoteHandler() {
return async (req: Request, res: Response): Promise<void> => {
try {
const { projectPath } = req.body;
if (!projectPath) {
res.status(400).json({ success: false, error: 'projectPath is required' });
return;
}
const status = await checkGitHubRemote(projectPath);
res.json({
success: true,
...status,
});
} catch (error) {
logError(error, 'Check GitHub remote failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
}

View File

@@ -0,0 +1,35 @@
/**
* Common utilities for GitHub routes
*/
import { exec } from 'child_process';
import { promisify } from 'util';
export const execAsync = promisify(exec);
// Extended PATH to include common tool installation locations
export const extendedPath = [
process.env.PATH,
'/opt/homebrew/bin',
'/usr/local/bin',
'/home/linuxbrew/.linuxbrew/bin',
`${process.env.HOME}/.local/bin`,
]
.filter(Boolean)
.join(':');
export const execEnv = {
...process.env,
PATH: extendedPath,
};
export function getErrorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message;
}
return String(error);
}
export function logError(error: unknown, context: string): void {
console.error(`[GitHub] ${context}:`, error);
}

View File

@@ -0,0 +1,89 @@
/**
* POST /list-issues endpoint - List GitHub issues for a project
*/
import type { Request, Response } from 'express';
import { execAsync, execEnv, getErrorMessage, logError } from './common.js';
import { checkGitHubRemote } from './check-github-remote.js';
export interface GitHubLabel {
name: string;
color: string;
}
export interface GitHubAuthor {
login: string;
}
export interface GitHubIssue {
number: number;
title: string;
state: string;
author: GitHubAuthor;
createdAt: string;
labels: GitHubLabel[];
url: string;
body: string;
}
export interface ListIssuesResult {
success: boolean;
issues?: GitHubIssue[];
openIssues?: GitHubIssue[];
closedIssues?: GitHubIssue[];
error?: string;
}
export function createListIssuesHandler() {
return async (req: Request, res: Response): Promise<void> => {
try {
const { projectPath } = req.body;
if (!projectPath) {
res.status(400).json({ success: false, error: 'projectPath is required' });
return;
}
// First check if this is a GitHub repo
const remoteStatus = await checkGitHubRemote(projectPath);
if (!remoteStatus.hasGitHubRemote) {
res.status(400).json({
success: false,
error: 'Project does not have a GitHub remote',
});
return;
}
// Fetch open issues
const { stdout: openStdout } = await execAsync(
'gh issue list --state open --json number,title,state,author,createdAt,labels,url,body --limit 100',
{
cwd: projectPath,
env: execEnv,
}
);
// Fetch closed issues
const { stdout: closedStdout } = await execAsync(
'gh issue list --state closed --json number,title,state,author,createdAt,labels,url,body --limit 50',
{
cwd: projectPath,
env: execEnv,
}
);
const openIssues: GitHubIssue[] = JSON.parse(openStdout || '[]');
const closedIssues: GitHubIssue[] = JSON.parse(closedStdout || '[]');
res.json({
success: true,
openIssues,
closedIssues,
issues: [...openIssues, ...closedIssues],
});
} catch (error) {
logError(error, 'List GitHub issues failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
}

View File

@@ -0,0 +1,93 @@
/**
* POST /list-prs endpoint - List GitHub pull requests for a project
*/
import type { Request, Response } from 'express';
import { execAsync, execEnv, getErrorMessage, logError } from './common.js';
import { checkGitHubRemote } from './check-github-remote.js';
export interface GitHubLabel {
name: string;
color: string;
}
export interface GitHubAuthor {
login: string;
}
export interface GitHubPR {
number: number;
title: string;
state: string;
author: GitHubAuthor;
createdAt: string;
labels: GitHubLabel[];
url: string;
isDraft: boolean;
headRefName: string;
reviewDecision: string | null;
mergeable: string;
body: string;
}
export interface ListPRsResult {
success: boolean;
prs?: GitHubPR[];
openPRs?: GitHubPR[];
mergedPRs?: GitHubPR[];
error?: string;
}
export function createListPRsHandler() {
return async (req: Request, res: Response): Promise<void> => {
try {
const { projectPath } = req.body;
if (!projectPath) {
res.status(400).json({ success: false, error: 'projectPath is required' });
return;
}
// First check if this is a GitHub repo
const remoteStatus = await checkGitHubRemote(projectPath);
if (!remoteStatus.hasGitHubRemote) {
res.status(400).json({
success: false,
error: 'Project does not have a GitHub remote',
});
return;
}
// Fetch open PRs
const { stdout: openStdout } = await execAsync(
'gh pr list --state open --json number,title,state,author,createdAt,labels,url,isDraft,headRefName,reviewDecision,mergeable,body --limit 100',
{
cwd: projectPath,
env: execEnv,
}
);
// Fetch merged PRs
const { stdout: mergedStdout } = await execAsync(
'gh pr list --state merged --json number,title,state,author,createdAt,labels,url,isDraft,headRefName,reviewDecision,mergeable,body --limit 50',
{
cwd: projectPath,
env: execEnv,
}
);
const openPRs: GitHubPR[] = JSON.parse(openStdout || '[]');
const mergedPRs: GitHubPR[] = JSON.parse(mergedStdout || '[]');
res.json({
success: true,
openPRs,
mergedPRs,
prs: [...openPRs, ...mergedPRs],
});
} catch (error) {
logError(error, 'List GitHub PRs failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
}

View File

@@ -23,10 +23,6 @@ const suggestionsSchema = {
id: { type: 'string' },
category: { type: 'string' },
description: { type: 'string' },
steps: {
type: 'array',
items: { type: 'string' },
},
priority: {
type: 'number',
minimum: 1,
@@ -34,7 +30,7 @@ const suggestionsSchema = {
},
reasoning: { type: 'string' },
},
required: ['category', 'description', 'steps', 'priority', 'reasoning'],
required: ['category', 'description', 'priority', 'reasoning'],
},
},
},
@@ -62,9 +58,8 @@ Look at the codebase and provide 3-5 concrete suggestions.
For each suggestion, provide:
1. A category (e.g., "User Experience", "Security", "Performance")
2. A clear description of what to implement
3. Concrete steps to implement it
4. Priority (1=high, 2=medium, 3=low)
5. Brief reasoning for why this would help
3. Priority (1=high, 2=medium, 3=low)
4. Brief reasoning for why this would help
The response will be automatically formatted as structured JSON.`;
@@ -164,7 +159,6 @@ The response will be automatically formatted as structured JSON.`;
id: `suggestion-${Date.now()}-0`,
category: 'Analysis',
description: 'Review the AI analysis output for insights',
steps: ['Review the generated analysis'],
priority: 1,
reasoning: 'The AI provided analysis but suggestions need manual review',
},