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

@@ -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);

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',
},

View File

@@ -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;
}