Skip to content

Commit 9586589

Browse files
author
Test User
committed
fixing auto verify for kanban issues
1 parent 9702f14 commit 9586589

30 files changed

Lines changed: 1376 additions & 306 deletions
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Security Audit
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- '*'
7+
push:
8+
branches:
9+
- main
10+
- master
11+
schedule:
12+
# Run weekly on Mondays at 9 AM UTC
13+
- cron: '0 9 * * 1'
14+
15+
jobs:
16+
audit:
17+
runs-on: ubuntu-latest
18+
19+
steps:
20+
- name: Checkout code
21+
uses: actions/checkout@v4
22+
23+
- name: Setup project
24+
uses: ./.github/actions/setup-project
25+
with:
26+
check-lockfile: 'true'
27+
28+
- name: Run npm audit
29+
run: npm audit --audit-level=moderate
30+
continue-on-error: false

apps/server/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import { SettingsService } from './services/settings-service.js';
4646
import { createSpecRegenerationRoutes } from './routes/app-spec/index.js';
4747
import { createClaudeRoutes } from './routes/claude/index.js';
4848
import { ClaudeUsageService } from './services/claude-usage-service.js';
49+
import { createGitHubRoutes } from './routes/github/index.js';
4950

5051
// Load environment variables
5152
dotenv.config();
@@ -145,6 +146,7 @@ app.use('/api/templates', createTemplatesRoutes());
145146
app.use('/api/terminal', createTerminalRoutes());
146147
app.use('/api/settings', createSettingsRoutes(settingsService));
147148
app.use('/api/claude', createClaudeRoutes(claudeUsageService));
149+
app.use('/api/github', createGitHubRoutes());
148150

149151
// Create HTTP server
150152
const server = createServer(app);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* GitHub routes - HTTP API for GitHub integration
3+
*/
4+
5+
import { Router } from 'express';
6+
import { createCheckGitHubRemoteHandler } from './routes/check-github-remote.js';
7+
import { createListIssuesHandler } from './routes/list-issues.js';
8+
import { createListPRsHandler } from './routes/list-prs.js';
9+
10+
export function createGitHubRoutes(): Router {
11+
const router = Router();
12+
13+
router.post('/check-remote', createCheckGitHubRemoteHandler());
14+
router.post('/issues', createListIssuesHandler());
15+
router.post('/prs', createListPRsHandler());
16+
17+
return router;
18+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* GET /check-github-remote endpoint - Check if project has a GitHub remote
3+
*/
4+
5+
import type { Request, Response } from 'express';
6+
import { execAsync, execEnv, getErrorMessage, logError } from './common.js';
7+
8+
export interface GitHubRemoteStatus {
9+
hasGitHubRemote: boolean;
10+
remoteUrl: string | null;
11+
owner: string | null;
12+
repo: string | null;
13+
}
14+
15+
export async function checkGitHubRemote(projectPath: string): Promise<GitHubRemoteStatus> {
16+
const status: GitHubRemoteStatus = {
17+
hasGitHubRemote: false,
18+
remoteUrl: null,
19+
owner: null,
20+
repo: null,
21+
};
22+
23+
try {
24+
// Get the remote URL (origin by default)
25+
const { stdout } = await execAsync('git remote get-url origin', {
26+
cwd: projectPath,
27+
env: execEnv,
28+
});
29+
30+
const remoteUrl = stdout.trim();
31+
status.remoteUrl = remoteUrl;
32+
33+
// Check if it's a GitHub URL
34+
// Formats: https://github.com/owner/repo.git, git@github.com:owner/repo.git
35+
const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([^/]+)\/([^/.]+)/);
36+
const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/([^/.]+)/);
37+
38+
const match = httpsMatch || sshMatch;
39+
if (match) {
40+
status.hasGitHubRemote = true;
41+
status.owner = match[1];
42+
status.repo = match[2].replace(/\.git$/, '');
43+
}
44+
} catch {
45+
// No remote or not a git repo - that's okay
46+
}
47+
48+
return status;
49+
}
50+
51+
export function createCheckGitHubRemoteHandler() {
52+
return async (req: Request, res: Response): Promise<void> => {
53+
try {
54+
const { projectPath } = req.body;
55+
56+
if (!projectPath) {
57+
res.status(400).json({ success: false, error: 'projectPath is required' });
58+
return;
59+
}
60+
61+
const status = await checkGitHubRemote(projectPath);
62+
res.json({
63+
success: true,
64+
...status,
65+
});
66+
} catch (error) {
67+
logError(error, 'Check GitHub remote failed');
68+
res.status(500).json({ success: false, error: getErrorMessage(error) });
69+
}
70+
};
71+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Common utilities for GitHub routes
3+
*/
4+
5+
import { exec } from 'child_process';
6+
import { promisify } from 'util';
7+
8+
export const execAsync = promisify(exec);
9+
10+
// Extended PATH to include common tool installation locations
11+
export const extendedPath = [
12+
process.env.PATH,
13+
'/opt/homebrew/bin',
14+
'/usr/local/bin',
15+
'/home/linuxbrew/.linuxbrew/bin',
16+
`${process.env.HOME}/.local/bin`,
17+
]
18+
.filter(Boolean)
19+
.join(':');
20+
21+
export const execEnv = {
22+
...process.env,
23+
PATH: extendedPath,
24+
};
25+
26+
export function getErrorMessage(error: unknown): string {
27+
if (error instanceof Error) {
28+
return error.message;
29+
}
30+
return String(error);
31+
}
32+
33+
export function logError(error: unknown, context: string): void {
34+
console.error(`[GitHub] ${context}:`, error);
35+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* POST /list-issues endpoint - List GitHub issues for a project
3+
*/
4+
5+
import type { Request, Response } from 'express';
6+
import { execAsync, execEnv, getErrorMessage, logError } from './common.js';
7+
import { checkGitHubRemote } from './check-github-remote.js';
8+
9+
export interface GitHubLabel {
10+
name: string;
11+
color: string;
12+
}
13+
14+
export interface GitHubAuthor {
15+
login: string;
16+
}
17+
18+
export interface GitHubIssue {
19+
number: number;
20+
title: string;
21+
state: string;
22+
author: GitHubAuthor;
23+
createdAt: string;
24+
labels: GitHubLabel[];
25+
url: string;
26+
body: string;
27+
}
28+
29+
export interface ListIssuesResult {
30+
success: boolean;
31+
issues?: GitHubIssue[];
32+
openIssues?: GitHubIssue[];
33+
closedIssues?: GitHubIssue[];
34+
error?: string;
35+
}
36+
37+
export function createListIssuesHandler() {
38+
return async (req: Request, res: Response): Promise<void> => {
39+
try {
40+
const { projectPath } = req.body;
41+
42+
if (!projectPath) {
43+
res.status(400).json({ success: false, error: 'projectPath is required' });
44+
return;
45+
}
46+
47+
// First check if this is a GitHub repo
48+
const remoteStatus = await checkGitHubRemote(projectPath);
49+
if (!remoteStatus.hasGitHubRemote) {
50+
res.status(400).json({
51+
success: false,
52+
error: 'Project does not have a GitHub remote',
53+
});
54+
return;
55+
}
56+
57+
// Fetch open issues
58+
const { stdout: openStdout } = await execAsync(
59+
'gh issue list --state open --json number,title,state,author,createdAt,labels,url,body --limit 100',
60+
{
61+
cwd: projectPath,
62+
env: execEnv,
63+
}
64+
);
65+
66+
// Fetch closed issues
67+
const { stdout: closedStdout } = await execAsync(
68+
'gh issue list --state closed --json number,title,state,author,createdAt,labels,url,body --limit 50',
69+
{
70+
cwd: projectPath,
71+
env: execEnv,
72+
}
73+
);
74+
75+
const openIssues: GitHubIssue[] = JSON.parse(openStdout || '[]');
76+
const closedIssues: GitHubIssue[] = JSON.parse(closedStdout || '[]');
77+
78+
res.json({
79+
success: true,
80+
openIssues,
81+
closedIssues,
82+
issues: [...openIssues, ...closedIssues],
83+
});
84+
} catch (error) {
85+
logError(error, 'List GitHub issues failed');
86+
res.status(500).json({ success: false, error: getErrorMessage(error) });
87+
}
88+
};
89+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* POST /list-prs endpoint - List GitHub pull requests for a project
3+
*/
4+
5+
import type { Request, Response } from 'express';
6+
import { execAsync, execEnv, getErrorMessage, logError } from './common.js';
7+
import { checkGitHubRemote } from './check-github-remote.js';
8+
9+
export interface GitHubLabel {
10+
name: string;
11+
color: string;
12+
}
13+
14+
export interface GitHubAuthor {
15+
login: string;
16+
}
17+
18+
export interface GitHubPR {
19+
number: number;
20+
title: string;
21+
state: string;
22+
author: GitHubAuthor;
23+
createdAt: string;
24+
labels: GitHubLabel[];
25+
url: string;
26+
isDraft: boolean;
27+
headRefName: string;
28+
reviewDecision: string | null;
29+
mergeable: string;
30+
body: string;
31+
}
32+
33+
export interface ListPRsResult {
34+
success: boolean;
35+
prs?: GitHubPR[];
36+
openPRs?: GitHubPR[];
37+
mergedPRs?: GitHubPR[];
38+
error?: string;
39+
}
40+
41+
export function createListPRsHandler() {
42+
return async (req: Request, res: Response): Promise<void> => {
43+
try {
44+
const { projectPath } = req.body;
45+
46+
if (!projectPath) {
47+
res.status(400).json({ success: false, error: 'projectPath is required' });
48+
return;
49+
}
50+
51+
// First check if this is a GitHub repo
52+
const remoteStatus = await checkGitHubRemote(projectPath);
53+
if (!remoteStatus.hasGitHubRemote) {
54+
res.status(400).json({
55+
success: false,
56+
error: 'Project does not have a GitHub remote',
57+
});
58+
return;
59+
}
60+
61+
// Fetch open PRs
62+
const { stdout: openStdout } = await execAsync(
63+
'gh pr list --state open --json number,title,state,author,createdAt,labels,url,isDraft,headRefName,reviewDecision,mergeable,body --limit 100',
64+
{
65+
cwd: projectPath,
66+
env: execEnv,
67+
}
68+
);
69+
70+
// Fetch merged PRs
71+
const { stdout: mergedStdout } = await execAsync(
72+
'gh pr list --state merged --json number,title,state,author,createdAt,labels,url,isDraft,headRefName,reviewDecision,mergeable,body --limit 50',
73+
{
74+
cwd: projectPath,
75+
env: execEnv,
76+
}
77+
);
78+
79+
const openPRs: GitHubPR[] = JSON.parse(openStdout || '[]');
80+
const mergedPRs: GitHubPR[] = JSON.parse(mergedStdout || '[]');
81+
82+
res.json({
83+
success: true,
84+
openPRs,
85+
mergedPRs,
86+
prs: [...openPRs, ...mergedPRs],
87+
});
88+
} catch (error) {
89+
logError(error, 'List GitHub PRs failed');
90+
res.status(500).json({ success: false, error: getErrorMessage(error) });
91+
}
92+
};
93+
}

apps/server/src/routes/suggestions/generate-suggestions.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,14 @@ const suggestionsSchema = {
2323
id: { type: 'string' },
2424
category: { type: 'string' },
2525
description: { type: 'string' },
26-
steps: {
27-
type: 'array',
28-
items: { type: 'string' },
29-
},
3026
priority: {
3127
type: 'number',
3228
minimum: 1,
3329
maximum: 3,
3430
},
3531
reasoning: { type: 'string' },
3632
},
37-
required: ['category', 'description', 'steps', 'priority', 'reasoning'],
33+
required: ['category', 'description', 'priority', 'reasoning'],
3834
},
3935
},
4036
},
@@ -62,9 +58,8 @@ Look at the codebase and provide 3-5 concrete suggestions.
6258
For each suggestion, provide:
6359
1. A category (e.g., "User Experience", "Security", "Performance")
6460
2. A clear description of what to implement
65-
3. Concrete steps to implement it
66-
4. Priority (1=high, 2=medium, 3=low)
67-
5. Brief reasoning for why this would help
61+
3. Priority (1=high, 2=medium, 3=low)
62+
4. Brief reasoning for why this would help
6863
6964
The response will be automatically formatted as structured JSON.`;
7065

@@ -164,7 +159,6 @@ The response will be automatically formatted as structured JSON.`;
164159
id: `suggestion-${Date.now()}-0`,
165160
category: 'Analysis',
166161
description: 'Review the AI analysis output for insights',
167-
steps: ['Review the generated analysis'],
168162
priority: 1,
169163
reasoning: 'The AI provided analysis but suggestions need manual review',
170164
},

0 commit comments

Comments
 (0)