diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 0f96eb68..7a9fa613 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -1,10 +1,3 @@ reviews: profile: assertive poem: false - auto_review: - base_branches: - - rc - - beta - - alpha - - production - - next \ No newline at end of file diff --git a/packages/tm-core/src/services/preflight-checker.service.ts b/packages/tm-core/src/services/preflight-checker.service.ts index 76ddb934..9531ee92 100644 --- a/packages/tm-core/src/services/preflight-checker.service.ts +++ b/packages/tm-core/src/services/preflight-checker.service.ts @@ -3,7 +3,7 @@ * Validates environment and prerequisites for autopilot execution */ -import { readFileSync } from 'fs'; +import { readFileSync, existsSync } from 'fs'; import { join } from 'path'; import { execSync } from 'child_process'; 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 */ async validateRequiredTools(): Promise { const tools: ToolCheck[] = []; - // Check git + // Always check git and gh CLI tools.push(this.checkTool('git', ['--version'])); + tools.push(await this.checkGhCli()); - // Check gh CLI - const ghAvailable = await isGhCliAvailable(this.projectRoot); - tools.push({ - name: 'gh', - available: ghAvailable, - message: ghAvailable ? 'GitHub CLI available' : 'GitHub CLI not available' - }); + // Detect project types and check their tools + const projectTypes = this.detectProjectTypes(); - // Check node - tools.push(this.checkTool('node', ['--version'])); + if (projectTypes.length === 0) { + logger.warn('No recognized project type detected'); + } else { + logger.info(`Detected project types: ${projectTypes.join(', ')}`); + } - // Check npm - tools.push(this.checkTool('npm', ['--version'])); + for (const type of projectTypes) { + const typeTools = this.getToolsForProjectType(type); + for (const tool of typeTools) { + tools.push(this.checkTool(tool.command, tool.args)); + } + } // Determine overall success 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 { + 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 */ @@ -280,9 +375,10 @@ export class PreflightChecker { if (defaultBranch.success) passed.push('Default branch'); else failed.push('Default branch'); + const total = passed.length + failed.length; const summary = allSuccess - ? `All preflight checks passed (${passed.length}/4)` - : `Preflight checks failed: ${failed.join(', ')} (${passed.length}/4 passed)`; + ? `All preflight checks passed (${passed.length}/${total})` + : `Preflight checks failed: ${failed.join(', ')} (${passed.length}/${total} passed)`; logger.info(summary); diff --git a/packages/tm-core/src/services/task-loader.service.ts b/packages/tm-core/src/services/task-loader.service.ts index 675280e8..4c98a34b 100644 --- a/packages/tm-core/src/services/task-loader.service.ts +++ b/packages/tm-core/src/services/task-loader.service.ts @@ -350,7 +350,7 @@ export class TaskLoaderService { // Keep adding subtasks whose dependencies are all completed while (ordered.length < task.subtasks.length) { - const added = false; + let added = false; for (const subtask of task.subtasks) { const subtaskId = String(subtask.id); @@ -368,6 +368,7 @@ export class TaskLoaderService { if (allDepsCompleted) { ordered.push(subtask); completed.add(subtaskId); + added = true; break; } } diff --git a/packages/tm-core/src/utils/git-utils.ts b/packages/tm-core/src/utils/git-utils.ts index 4c7733df..5dacc803 100644 --- a/packages/tm-core/src/utils/git-utils.ts +++ b/packages/tm-core/src/utils/git-utils.ts @@ -102,7 +102,7 @@ export async function getLocalBranches(projectRoot: string): Promise { try { const { stdout } = await execAsync( 'git branch --format="%(refname:short)"', - { cwd: projectRoot } + { cwd: projectRoot, maxBuffer: 10 * 1024 * 1024 } ); return stdout .trim() @@ -127,13 +127,13 @@ export async function getRemoteBranches( try { const { stdout } = await execAsync( 'git branch -r --format="%(refname:short)"', - { cwd: projectRoot } + { cwd: projectRoot, maxBuffer: 10 * 1024 * 1024 } ); return stdout .trim() .split('\n') .filter((branch) => branch.length > 0 && !branch.includes('HEAD')) - .map((branch) => branch.replace(/^origin\//, '').trim()); + .map((branch) => branch.replace(/^[^/]+\//, '').trim()); } catch (error) { return []; } @@ -212,19 +212,41 @@ export async function getDefaultBranch( } } - // Fallback to git remote info - const { stdout } = await execAsync( - 'git symbolic-ref refs/remotes/origin/HEAD', - { cwd: projectRoot } - ); - return stdout.replace('refs/remotes/origin/', '').trim(); + // Fallback to git remote info (support non-origin remotes) + const remotesRaw = await execAsync('git remote', { cwd: projectRoot }); + const remotes = remotesRaw.stdout.trim().split('\n').filter(Boolean); + if (remotes.length > 0) { + const primary = remotes.includes('origin') ? 'origin' : remotes[0]; + // 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) { // Final fallback - common default branch names const commonDefaults = ['main', 'master']; const branches = await getLocalBranches(projectRoot); + const remoteBranches = await getRemoteBranches(projectRoot); for (const defaultName of commonDefaults) { - if (branches.includes(defaultName)) { + if ( + branches.includes(defaultName) || + remoteBranches.includes(defaultName) + ) { return defaultName; } } @@ -279,7 +301,7 @@ export function sanitizeBranchNameForTag(branchName: string): string { // Replace invalid characters with hyphens and clean up 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, '-') // Collapse multiple hyphens .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 - const reservedBranches = ['main', 'master', 'develop', 'dev', 'HEAD']; + const reservedBranches = ['main', 'master', 'develop', 'dev', 'head']; if (reservedBranches.includes(branchName.toLowerCase())) { return false; } diff --git a/scripts/create-worktree.sh b/scripts/create-worktree.sh index 961ad931..2674104f 100755 --- a/scripts/create-worktree.sh +++ b/scripts/create-worktree.sh @@ -3,15 +3,20 @@ # Create a git worktree for parallel Claude Code development # Usage: ./scripts/create-worktree.sh [branch-name] -set -e +set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" WORKTREES_DIR="$(cd "$PROJECT_ROOT/.." && pwd)/claude-task-master-worktrees" +cd "$PROJECT_ROOT" # Get branch name (default to current branch with auto/ prefix) if [ -z "$1" ]; then 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 " + exit 1 + fi BRANCH_NAME="auto/$CURRENT_BRANCH" echo "No branch specified, using: $BRANCH_NAME" else @@ -36,9 +41,14 @@ if [ -d "$WORKTREE_PATH" ]; then exit 1 fi -# Create new branch and worktree -git worktree add -b "$BRANCH_NAME" "$WORKTREE_PATH" 2>/dev/null || \ - git worktree add "$WORKTREE_PATH" "$BRANCH_NAME" +# Create worktree (new or existing branch) +if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then + 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 "✅ Worktree created successfully!"