mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 08:13:37 +00:00
fixing auto verify for kanban issues
This commit is contained in:
@@ -46,6 +46,7 @@ import { SettingsService } from './services/settings-service.js';
|
||||
import { createSpecRegenerationRoutes } from './routes/app-spec/index.js';
|
||||
import { createClaudeRoutes } from './routes/claude/index.js';
|
||||
import { ClaudeUsageService } from './services/claude-usage-service.js';
|
||||
import { createGitHubRoutes } from './routes/github/index.js';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
@@ -145,6 +146,7 @@ app.use('/api/templates', createTemplatesRoutes());
|
||||
app.use('/api/terminal', createTerminalRoutes());
|
||||
app.use('/api/settings', createSettingsRoutes(settingsService));
|
||||
app.use('/api/claude', createClaudeRoutes(claudeUsageService));
|
||||
app.use('/api/github', createGitHubRoutes());
|
||||
|
||||
// Create HTTP server
|
||||
const server = createServer(app);
|
||||
|
||||
18
apps/server/src/routes/github/index.ts
Normal file
18
apps/server/src/routes/github/index.ts
Normal 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;
|
||||
}
|
||||
71
apps/server/src/routes/github/routes/check-github-remote.ts
Normal file
71
apps/server/src/routes/github/routes/check-github-remote.ts
Normal 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) });
|
||||
}
|
||||
};
|
||||
}
|
||||
35
apps/server/src/routes/github/routes/common.ts
Normal file
35
apps/server/src/routes/github/routes/common.ts
Normal 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);
|
||||
}
|
||||
89
apps/server/src/routes/github/routes/list-issues.ts
Normal file
89
apps/server/src/routes/github/routes/list-issues.ts
Normal 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) });
|
||||
}
|
||||
};
|
||||
}
|
||||
93
apps/server/src/routes/github/routes/list-prs.ts
Normal file
93
apps/server/src/routes/github/routes/list-prs.ts
Normal 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) });
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -599,15 +599,18 @@ export class AutoModeService {
|
||||
}
|
||||
);
|
||||
|
||||
// Mark as waiting_approval for user review
|
||||
await this.updateFeatureStatus(projectPath, featureId, 'waiting_approval');
|
||||
// Determine final status based on testing mode:
|
||||
// - skipTests=false (automated testing): go directly to 'verified' (no manual verify needed)
|
||||
// - skipTests=true (manual verification): go to 'waiting_approval' for manual review
|
||||
const finalStatus = feature.skipTests ? 'waiting_approval' : 'verified';
|
||||
await this.updateFeatureStatus(projectPath, featureId, finalStatus);
|
||||
|
||||
this.emitAutoModeEvent('auto_mode_feature_complete', {
|
||||
featureId,
|
||||
passes: true,
|
||||
message: `Feature completed in ${Math.round(
|
||||
(Date.now() - tempRunningFeature.startTime) / 1000
|
||||
)}s`,
|
||||
)}s${finalStatus === 'verified' ? ' - auto-verified' : ''}`,
|
||||
projectPath,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -868,13 +871,16 @@ Address the follow-up instructions above. Review the previous work and make the
|
||||
}
|
||||
);
|
||||
|
||||
// Mark as waiting_approval for user review
|
||||
await this.updateFeatureStatus(projectPath, featureId, 'waiting_approval');
|
||||
// Determine final status based on testing mode:
|
||||
// - skipTests=false (automated testing): go directly to 'verified' (no manual verify needed)
|
||||
// - skipTests=true (manual verification): go to 'waiting_approval' for manual review
|
||||
const finalStatus = feature?.skipTests ? 'waiting_approval' : 'verified';
|
||||
await this.updateFeatureStatus(projectPath, featureId, finalStatus);
|
||||
|
||||
this.emitAutoModeEvent('auto_mode_feature_complete', {
|
||||
featureId,
|
||||
passes: true,
|
||||
message: 'Follow-up completed successfully',
|
||||
message: `Follow-up completed successfully${finalStatus === 'verified' ? ' - auto-verified' : ''}`,
|
||||
projectPath,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -1652,15 +1658,17 @@ You can use the Read tool to view these images at any time during implementation
|
||||
`;
|
||||
}
|
||||
|
||||
prompt += `
|
||||
// Add verification instructions based on testing mode
|
||||
if (feature.skipTests) {
|
||||
// Manual verification - just implement the feature
|
||||
prompt += `
|
||||
## Instructions
|
||||
|
||||
Implement this feature by:
|
||||
1. First, explore the codebase to understand the existing structure
|
||||
2. Plan your implementation approach
|
||||
3. Write the necessary code changes
|
||||
4. Add or update tests as needed
|
||||
5. Ensure the code follows existing patterns and conventions
|
||||
4. Ensure the code follows existing patterns and conventions
|
||||
|
||||
When done, wrap your final summary in <summary> tags like this:
|
||||
|
||||
@@ -1678,6 +1686,56 @@ When done, wrap your final summary in <summary> tags like this:
|
||||
</summary>
|
||||
|
||||
This helps parse your summary correctly in the output logs.`;
|
||||
} else {
|
||||
// Automated testing - implement and verify with Playwright
|
||||
prompt += `
|
||||
## Instructions
|
||||
|
||||
Implement this feature by:
|
||||
1. First, explore the codebase to understand the existing structure
|
||||
2. Plan your implementation approach
|
||||
3. Write the necessary code changes
|
||||
4. Ensure the code follows existing patterns and conventions
|
||||
|
||||
## Verification with Playwright (REQUIRED)
|
||||
|
||||
After implementing the feature, you MUST verify it works correctly using Playwright:
|
||||
|
||||
1. **Create a temporary Playwright test** to verify the feature works as expected
|
||||
2. **Run the test** to confirm the feature is working
|
||||
3. **Delete the test file** after verification - this is a temporary verification test, not a permanent test suite addition
|
||||
|
||||
Example verification workflow:
|
||||
\`\`\`bash
|
||||
# Create a simple verification test
|
||||
npx playwright test my-verification-test.spec.ts
|
||||
|
||||
# After successful verification, delete the test
|
||||
rm my-verification-test.spec.ts
|
||||
\`\`\`
|
||||
|
||||
The test should verify the core functionality of the feature. If the test fails, fix the implementation and re-test.
|
||||
|
||||
When done, wrap your final summary in <summary> tags like this:
|
||||
|
||||
<summary>
|
||||
## Summary: [Feature Title]
|
||||
|
||||
### Changes Implemented
|
||||
- [List of changes made]
|
||||
|
||||
### Files Modified
|
||||
- [List of files]
|
||||
|
||||
### Verification Status
|
||||
- [Describe how the feature was verified with Playwright]
|
||||
|
||||
### Notes for Developer
|
||||
- [Any important notes]
|
||||
</summary>
|
||||
|
||||
This helps parse your summary correctly in the output logs.`;
|
||||
}
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user