chore: apply requested changes and improve coderabbit config

This commit is contained in:
Ralph Khreish
2025-10-08 14:47:23 +02:00
parent f71cdb4eaa
commit 4b6ad19bc4
5 changed files with 161 additions and 39 deletions

View File

@@ -1,10 +1,3 @@
reviews: reviews:
profile: assertive profile: assertive
poem: false poem: false
auto_review:
base_branches:
- rc
- beta
- alpha
- production
- next

View File

@@ -3,7 +3,7 @@
* Validates environment and prerequisites for autopilot execution * Validates environment and prerequisites for autopilot execution
*/ */
import { readFileSync } from 'fs'; import { readFileSync, existsSync } from 'fs';
import { join } from 'path'; import { join } from 'path';
import { execSync } from 'child_process'; import { execSync } from 'child_process';
import { getLogger } from '../logger/factory.js'; import { getLogger } from '../logger/factory.js';
@@ -147,28 +147,97 @@ export class PreflightChecker {
} }
} }
/**
* Detect project types based on common configuration files
*/
private detectProjectTypes(): string[] {
const types: string[] = [];
if (existsSync(join(this.projectRoot, 'package.json'))) types.push('node');
if (
existsSync(join(this.projectRoot, 'requirements.txt')) ||
existsSync(join(this.projectRoot, 'setup.py')) ||
existsSync(join(this.projectRoot, 'pyproject.toml'))
)
types.push('python');
if (
existsSync(join(this.projectRoot, 'pom.xml')) ||
existsSync(join(this.projectRoot, 'build.gradle'))
)
types.push('java');
if (existsSync(join(this.projectRoot, 'go.mod'))) types.push('go');
if (existsSync(join(this.projectRoot, 'Cargo.toml'))) types.push('rust');
if (existsSync(join(this.projectRoot, 'composer.json'))) types.push('php');
if (existsSync(join(this.projectRoot, 'Gemfile'))) types.push('ruby');
if (
existsSync(join(this.projectRoot, '*.csproj')) ||
existsSync(join(this.projectRoot, '*.sln'))
)
types.push('dotnet');
return types;
}
/**
* Get required tools for a project type
*/
private getToolsForProjectType(
type: string
): Array<{ command: string; args: string[] }> {
const toolMap: Record<
string,
Array<{ command: string; args: string[] }>
> = {
node: [
{ command: 'node', args: ['--version'] },
{ command: 'npm', args: ['--version'] }
],
python: [
{ command: 'python3', args: ['--version'] },
{ command: 'pip3', args: ['--version'] }
],
java: [{ command: 'java', args: ['--version'] }],
go: [{ command: 'go', args: ['version'] }],
rust: [{ command: 'cargo', args: ['--version'] }],
php: [
{ command: 'php', args: ['--version'] },
{ command: 'composer', args: ['--version'] }
],
ruby: [
{ command: 'ruby', args: ['--version'] },
{ command: 'bundle', args: ['--version'] }
],
dotnet: [{ command: 'dotnet', args: ['--version'] }]
};
return toolMap[type] || [];
}
/** /**
* Validate required tools availability * Validate required tools availability
*/ */
async validateRequiredTools(): Promise<CheckResult> { async validateRequiredTools(): Promise<CheckResult> {
const tools: ToolCheck[] = []; const tools: ToolCheck[] = [];
// Check git // Always check git and gh CLI
tools.push(this.checkTool('git', ['--version'])); tools.push(this.checkTool('git', ['--version']));
tools.push(await this.checkGhCli());
// Check gh CLI // Detect project types and check their tools
const ghAvailable = await isGhCliAvailable(this.projectRoot); const projectTypes = this.detectProjectTypes();
tools.push({
name: 'gh',
available: ghAvailable,
message: ghAvailable ? 'GitHub CLI available' : 'GitHub CLI not available'
});
// Check node if (projectTypes.length === 0) {
tools.push(this.checkTool('node', ['--version'])); logger.warn('No recognized project type detected');
} else {
logger.info(`Detected project types: ${projectTypes.join(', ')}`);
}
// Check npm for (const type of projectTypes) {
tools.push(this.checkTool('npm', ['--version'])); const typeTools = this.getToolsForProjectType(type);
for (const tool of typeTools) {
tools.push(this.checkTool(tool.command, tool.args));
}
}
// Determine overall success // Determine overall success
const allAvailable = tools.every((tool) => tool.available); const allAvailable = tools.every((tool) => tool.available);
@@ -219,6 +288,32 @@ export class PreflightChecker {
} }
} }
/**
* Check GitHub CLI installation and authentication status
*/
private async checkGhCli(): Promise<ToolCheck> {
try {
const version = execSync('gh --version', {
cwd: this.projectRoot,
encoding: 'utf-8',
stdio: 'pipe'
})
.trim()
.split('\n')[0];
const authed = await isGhCliAvailable(this.projectRoot);
return {
name: 'gh',
available: true,
version,
message: authed
? 'GitHub CLI installed (authenticated)'
: 'GitHub CLI installed (not authenticated)'
};
} catch {
return { name: 'gh', available: false, message: 'GitHub CLI not found' };
}
}
/** /**
* Detect default branch * Detect default branch
*/ */
@@ -280,9 +375,10 @@ export class PreflightChecker {
if (defaultBranch.success) passed.push('Default branch'); if (defaultBranch.success) passed.push('Default branch');
else failed.push('Default branch'); else failed.push('Default branch');
const total = passed.length + failed.length;
const summary = allSuccess const summary = allSuccess
? `All preflight checks passed (${passed.length}/4)` ? `All preflight checks passed (${passed.length}/${total})`
: `Preflight checks failed: ${failed.join(', ')} (${passed.length}/4 passed)`; : `Preflight checks failed: ${failed.join(', ')} (${passed.length}/${total} passed)`;
logger.info(summary); logger.info(summary);

View File

@@ -350,7 +350,7 @@ export class TaskLoaderService {
// Keep adding subtasks whose dependencies are all completed // Keep adding subtasks whose dependencies are all completed
while (ordered.length < task.subtasks.length) { while (ordered.length < task.subtasks.length) {
const added = false; let added = false;
for (const subtask of task.subtasks) { for (const subtask of task.subtasks) {
const subtaskId = String(subtask.id); const subtaskId = String(subtask.id);
@@ -368,6 +368,7 @@ export class TaskLoaderService {
if (allDepsCompleted) { if (allDepsCompleted) {
ordered.push(subtask); ordered.push(subtask);
completed.add(subtaskId); completed.add(subtaskId);
added = true;
break; break;
} }
} }

View File

@@ -102,7 +102,7 @@ export async function getLocalBranches(projectRoot: string): Promise<string[]> {
try { try {
const { stdout } = await execAsync( const { stdout } = await execAsync(
'git branch --format="%(refname:short)"', 'git branch --format="%(refname:short)"',
{ cwd: projectRoot } { cwd: projectRoot, maxBuffer: 10 * 1024 * 1024 }
); );
return stdout return stdout
.trim() .trim()
@@ -127,13 +127,13 @@ export async function getRemoteBranches(
try { try {
const { stdout } = await execAsync( const { stdout } = await execAsync(
'git branch -r --format="%(refname:short)"', 'git branch -r --format="%(refname:short)"',
{ cwd: projectRoot } { cwd: projectRoot, maxBuffer: 10 * 1024 * 1024 }
); );
return stdout return stdout
.trim() .trim()
.split('\n') .split('\n')
.filter((branch) => branch.length > 0 && !branch.includes('HEAD')) .filter((branch) => branch.length > 0 && !branch.includes('HEAD'))
.map((branch) => branch.replace(/^origin\//, '').trim()); .map((branch) => branch.replace(/^[^/]+\//, '').trim());
} catch (error) { } catch (error) {
return []; return [];
} }
@@ -212,19 +212,41 @@ export async function getDefaultBranch(
} }
} }
// Fallback to git remote info // Fallback to git remote info (support non-origin remotes)
const { stdout } = await execAsync( const remotesRaw = await execAsync('git remote', { cwd: projectRoot });
'git symbolic-ref refs/remotes/origin/HEAD', const remotes = remotesRaw.stdout.trim().split('\n').filter(Boolean);
{ cwd: projectRoot } if (remotes.length > 0) {
); const primary = remotes.includes('origin') ? 'origin' : remotes[0];
return stdout.replace('refs/remotes/origin/', '').trim(); // Parse `git remote show` (preferred)
try {
const { stdout } = await execAsync(`git remote show ${primary}`, {
cwd: projectRoot
});
const m = stdout.match(/HEAD branch:\s+([^\s]+)/);
if (m) return m[1].trim();
} catch {}
// Fallback to symbolic-ref of remote HEAD
try {
const { stdout } = await execAsync(
`git symbolic-ref refs/remotes/${primary}/HEAD`,
{ cwd: projectRoot }
);
return stdout.replace(`refs/remotes/${primary}/`, '').trim();
} catch {}
}
// If we couldn't determine, throw to trigger final fallbacks
throw new Error('default-branch-not-found');
} catch (error) { } catch (error) {
// Final fallback - common default branch names // Final fallback - common default branch names
const commonDefaults = ['main', 'master']; const commonDefaults = ['main', 'master'];
const branches = await getLocalBranches(projectRoot); const branches = await getLocalBranches(projectRoot);
const remoteBranches = await getRemoteBranches(projectRoot);
for (const defaultName of commonDefaults) { for (const defaultName of commonDefaults) {
if (branches.includes(defaultName)) { if (
branches.includes(defaultName) ||
remoteBranches.includes(defaultName)
) {
return defaultName; return defaultName;
} }
} }
@@ -279,7 +301,7 @@ export function sanitizeBranchNameForTag(branchName: string): string {
// Replace invalid characters with hyphens and clean up // Replace invalid characters with hyphens and clean up
return branchName return branchName
.replace(/[^a-zA-Z0-9_-]/g, '-') // Replace invalid chars with hyphens .replace(/[^a-zA-Z0-9_.-]/g, '-') // Replace invalid chars with hyphens (allow dots)
.replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens .replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
.replace(/-+/g, '-') // Collapse multiple hyphens .replace(/-+/g, '-') // Collapse multiple hyphens
.toLowerCase() // Convert to lowercase .toLowerCase() // Convert to lowercase
@@ -295,7 +317,7 @@ export function isValidBranchForTag(branchName: string): boolean {
} }
// Check if it's a reserved branch name that shouldn't become tags // Check if it's a reserved branch name that shouldn't become tags
const reservedBranches = ['main', 'master', 'develop', 'dev', 'HEAD']; const reservedBranches = ['main', 'master', 'develop', 'dev', 'head'];
if (reservedBranches.includes(branchName.toLowerCase())) { if (reservedBranches.includes(branchName.toLowerCase())) {
return false; return false;
} }

View File

@@ -3,15 +3,20 @@
# Create a git worktree for parallel Claude Code development # Create a git worktree for parallel Claude Code development
# Usage: ./scripts/create-worktree.sh [branch-name] # Usage: ./scripts/create-worktree.sh [branch-name]
set -e set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
WORKTREES_DIR="$(cd "$PROJECT_ROOT/.." && pwd)/claude-task-master-worktrees" WORKTREES_DIR="$(cd "$PROJECT_ROOT/.." && pwd)/claude-task-master-worktrees"
cd "$PROJECT_ROOT"
# Get branch name (default to current branch with auto/ prefix) # Get branch name (default to current branch with auto/ prefix)
if [ -z "$1" ]; then if [ -z "$1" ]; then
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [ "$CURRENT_BRANCH" = "HEAD" ]; then
echo "Detached HEAD detected. Please specify a branch: ./scripts/create-worktree.sh <branch-name>"
exit 1
fi
BRANCH_NAME="auto/$CURRENT_BRANCH" BRANCH_NAME="auto/$CURRENT_BRANCH"
echo "No branch specified, using: $BRANCH_NAME" echo "No branch specified, using: $BRANCH_NAME"
else else
@@ -36,9 +41,14 @@ if [ -d "$WORKTREE_PATH" ]; then
exit 1 exit 1
fi fi
# Create new branch and worktree # Create worktree (new or existing branch)
git worktree add -b "$BRANCH_NAME" "$WORKTREE_PATH" 2>/dev/null || \ if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then
git worktree add "$WORKTREE_PATH" "$BRANCH_NAME" git worktree add "$WORKTREE_PATH" "$BRANCH_NAME"
elif git ls-remote --exit-code --heads origin "$BRANCH_NAME" >/dev/null 2>&1; then
git worktree add "$WORKTREE_PATH" "origin/$BRANCH_NAME"
else
git worktree add -b "$BRANCH_NAME" "$WORKTREE_PATH"
fi
echo "" echo ""
echo "✅ Worktree created successfully!" echo "✅ Worktree created successfully!"