Fix branch logic for merge AI
This commit is contained in:
@@ -125,6 +125,11 @@ export class AgentManager extends EventEmitter {
|
|||||||
// Force: When user starts a task from the UI, that IS their approval
|
// Force: When user starts a task from the UI, that IS their approval
|
||||||
args.push('--force');
|
args.push('--force');
|
||||||
|
|
||||||
|
// Pass base branch if specified (ensures worktrees are created from the correct branch)
|
||||||
|
if (options.baseBranch) {
|
||||||
|
args.push('--base-branch', options.baseBranch);
|
||||||
|
}
|
||||||
|
|
||||||
// Note: --parallel was removed from run.py CLI - parallel execution is handled internally by the agent
|
// Note: --parallel was removed from run.py CLI - parallel execution is handled internally by the agent
|
||||||
// The options.parallel and options.workers are kept for future use or logging purposes
|
// The options.parallel and options.workers are kept for future use or logging purposes
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export interface IdeationConfig {
|
|||||||
export interface TaskExecutionOptions {
|
export interface TaskExecutionOptions {
|
||||||
parallel?: boolean;
|
parallel?: boolean;
|
||||||
workers?: number;
|
workers?: number;
|
||||||
|
baseBranch?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpecCreationMetadata {
|
export interface SpecCreationMetadata {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ipcMain, app } from 'electron';
|
import { ipcMain, app } from 'electron';
|
||||||
import { existsSync, readFileSync } from 'fs';
|
import { existsSync, readFileSync } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { execSync } from 'child_process';
|
||||||
import { IPC_CHANNELS } from '../../shared/constants';
|
import { IPC_CHANNELS } from '../../shared/constants';
|
||||||
import type {
|
import type {
|
||||||
Project,
|
Project,
|
||||||
@@ -22,6 +23,79 @@ import { insightsService } from '../insights-service';
|
|||||||
import { titleGenerator } from '../title-generator';
|
import { titleGenerator } from '../title-generator';
|
||||||
import type { BrowserWindow } from 'electron';
|
import type { BrowserWindow } from 'electron';
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Git Helper Functions
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of git branches for a directory
|
||||||
|
*/
|
||||||
|
function getGitBranches(projectPath: string): string[] {
|
||||||
|
try {
|
||||||
|
const result = execSync('git branch --list --format="%(refname:short)"', {
|
||||||
|
cwd: projectPath,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe']
|
||||||
|
});
|
||||||
|
return result.trim().split('\n').filter(b => b.trim());
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current git branch for a directory
|
||||||
|
*/
|
||||||
|
function getCurrentGitBranch(projectPath: string): string | null {
|
||||||
|
try {
|
||||||
|
const result = execSync('git rev-parse --abbrev-ref HEAD', {
|
||||||
|
cwd: projectPath,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe']
|
||||||
|
});
|
||||||
|
return result.trim() || null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect the main branch for a git repository
|
||||||
|
* Checks for common main branch names in order of preference
|
||||||
|
*/
|
||||||
|
function detectMainBranch(projectPath: string): string | null {
|
||||||
|
const branches = getGitBranches(projectPath);
|
||||||
|
if (branches.length === 0) return null;
|
||||||
|
|
||||||
|
// Check for common main branch names in order of preference
|
||||||
|
const mainBranchCandidates = ['main', 'master', 'develop', 'dev', 'trunk'];
|
||||||
|
for (const candidate of mainBranchCandidates) {
|
||||||
|
if (branches.includes(candidate)) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If none of the common names found, check for origin/HEAD reference
|
||||||
|
try {
|
||||||
|
const result = execSync('git symbolic-ref refs/remotes/origin/HEAD', {
|
||||||
|
cwd: projectPath,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe']
|
||||||
|
});
|
||||||
|
const ref = result.trim();
|
||||||
|
// Extract branch name from refs/remotes/origin/main
|
||||||
|
const match = ref.match(/refs\/remotes\/origin\/(.+)/);
|
||||||
|
if (match && branches.includes(match[1])) {
|
||||||
|
return match[1];
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// origin/HEAD not set, continue with fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: return the first branch (usually the current one)
|
||||||
|
return branches[0] || null;
|
||||||
|
}
|
||||||
|
|
||||||
const settingsPath = path.join(app.getPath('userData'), 'settings.json');
|
const settingsPath = path.join(app.getPath('userData'), 'settings.json');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -330,4 +404,65 @@ export function registerProjectHandlers(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Git Operations
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
// Get all branches for a project
|
||||||
|
ipcMain.handle(
|
||||||
|
IPC_CHANNELS.GIT_GET_BRANCHES,
|
||||||
|
async (_, projectPath: string): Promise<IPCResult<string[]>> => {
|
||||||
|
try {
|
||||||
|
if (!existsSync(projectPath)) {
|
||||||
|
return { success: false, error: 'Directory does not exist' };
|
||||||
|
}
|
||||||
|
const branches = getGitBranches(projectPath);
|
||||||
|
return { success: true, data: branches };
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get current branch for a project
|
||||||
|
ipcMain.handle(
|
||||||
|
IPC_CHANNELS.GIT_GET_CURRENT_BRANCH,
|
||||||
|
async (_, projectPath: string): Promise<IPCResult<string | null>> => {
|
||||||
|
try {
|
||||||
|
if (!existsSync(projectPath)) {
|
||||||
|
return { success: false, error: 'Directory does not exist' };
|
||||||
|
}
|
||||||
|
const branch = getCurrentGitBranch(projectPath);
|
||||||
|
return { success: true, data: branch };
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Auto-detect main branch for a project
|
||||||
|
ipcMain.handle(
|
||||||
|
IPC_CHANNELS.GIT_DETECT_MAIN_BRANCH,
|
||||||
|
async (_, projectPath: string): Promise<IPCResult<string | null>> => {
|
||||||
|
try {
|
||||||
|
if (!existsSync(projectPath)) {
|
||||||
|
return { success: false, error: 'Directory does not exist' };
|
||||||
|
}
|
||||||
|
const mainBranch = detectMainBranch(projectPath);
|
||||||
|
return { success: true, data: mainBranch };
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ export function registerTaskExecutionHandlers(
|
|||||||
|
|
||||||
console.log('[TASK_START] hasSpec:', hasSpec, 'needsSpecCreation:', needsSpecCreation, 'needsImplementation:', needsImplementation);
|
console.log('[TASK_START] hasSpec:', hasSpec, 'needsSpecCreation:', needsSpecCreation, 'needsImplementation:', needsImplementation);
|
||||||
|
|
||||||
|
// Get base branch from project settings for worktree creation
|
||||||
|
const baseBranch = project.settings?.mainBranch;
|
||||||
|
|
||||||
if (needsSpecCreation) {
|
if (needsSpecCreation) {
|
||||||
// No spec file - need to run spec_runner.py to create the spec
|
// No spec file - need to run spec_runner.py to create the spec
|
||||||
const taskDescription = task.description || task.title;
|
const taskDescription = task.description || task.title;
|
||||||
@@ -89,7 +92,8 @@ export function registerTaskExecutionHandlers(
|
|||||||
task.specId,
|
task.specId,
|
||||||
{
|
{
|
||||||
parallel: false, // Sequential for planning phase
|
parallel: false, // Sequential for planning phase
|
||||||
workers: 1
|
workers: 1,
|
||||||
|
baseBranch
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -103,7 +107,8 @@ export function registerTaskExecutionHandlers(
|
|||||||
task.specId,
|
task.specId,
|
||||||
{
|
{
|
||||||
parallel: false,
|
parallel: false,
|
||||||
workers: 1
|
workers: 1,
|
||||||
|
baseBranch
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,11 @@ export interface ProjectAPI {
|
|||||||
falkorDbUri: string,
|
falkorDbUri: string,
|
||||||
openAiApiKey: string
|
openAiApiKey: string
|
||||||
) => Promise<IPCResult<GraphitiConnectionTestResult>>;
|
) => Promise<IPCResult<GraphitiConnectionTestResult>>;
|
||||||
|
|
||||||
|
// Git Operations
|
||||||
|
getGitBranches: (projectPath: string) => Promise<IPCResult<string[]>>;
|
||||||
|
getCurrentGitBranch: (projectPath: string) => Promise<IPCResult<string | null>>;
|
||||||
|
detectMainBranch: (projectPath: string) => Promise<IPCResult<string | null>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createProjectAPI = (): ProjectAPI => ({
|
export const createProjectAPI = (): ProjectAPI => ({
|
||||||
@@ -165,5 +170,15 @@ export const createProjectAPI = (): ProjectAPI => ({
|
|||||||
falkorDbUri: string,
|
falkorDbUri: string,
|
||||||
openAiApiKey: string
|
openAiApiKey: string
|
||||||
): Promise<IPCResult<GraphitiConnectionTestResult>> =>
|
): Promise<IPCResult<GraphitiConnectionTestResult>> =>
|
||||||
ipcRenderer.invoke(IPC_CHANNELS.GRAPHITI_TEST_CONNECTION, falkorDbUri, openAiApiKey)
|
ipcRenderer.invoke(IPC_CHANNELS.GRAPHITI_TEST_CONNECTION, falkorDbUri, openAiApiKey),
|
||||||
|
|
||||||
|
// Git Operations
|
||||||
|
getGitBranches: (projectPath: string): Promise<IPCResult<string[]>> =>
|
||||||
|
ipcRenderer.invoke(IPC_CHANNELS.GIT_GET_BRANCHES, projectPath),
|
||||||
|
|
||||||
|
getCurrentGitBranch: (projectPath: string): Promise<IPCResult<string | null>> =>
|
||||||
|
ipcRenderer.invoke(IPC_CHANNELS.GIT_GET_CURRENT_BRANCH, projectPath),
|
||||||
|
|
||||||
|
detectMainBranch: (projectPath: string): Promise<IPCResult<string | null>> =>
|
||||||
|
ipcRenderer.invoke(IPC_CHANNELS.GIT_DETECT_MAIN_BRANCH, projectPath)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -63,6 +63,17 @@ export function AddProjectModal({ open, onOpenChange, onProjectAdded }: AddProje
|
|||||||
if (path) {
|
if (path) {
|
||||||
const project = await addProject(path);
|
const project = await addProject(path);
|
||||||
if (project) {
|
if (project) {
|
||||||
|
// Auto-detect and save the main branch for the project
|
||||||
|
try {
|
||||||
|
const mainBranchResult = await window.electronAPI.detectMainBranch(path);
|
||||||
|
if (mainBranchResult.success && mainBranchResult.data) {
|
||||||
|
await window.electronAPI.updateProjectSettings(project.id, {
|
||||||
|
mainBranch: mainBranchResult.data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Non-fatal - main branch can be set later in settings
|
||||||
|
}
|
||||||
onProjectAdded?.(project, !project.autoBuildPath);
|
onProjectAdded?.(project, !project.autoBuildPath);
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
}
|
}
|
||||||
@@ -112,6 +123,20 @@ export function AddProjectModal({ open, onOpenChange, onProjectAdded }: AddProje
|
|||||||
// Add the project to our store
|
// Add the project to our store
|
||||||
const project = await addProject(result.data.path);
|
const project = await addProject(result.data.path);
|
||||||
if (project) {
|
if (project) {
|
||||||
|
// For new projects with git init, set main branch
|
||||||
|
// Git init creates 'main' branch by default on modern git
|
||||||
|
if (initGit) {
|
||||||
|
try {
|
||||||
|
const mainBranchResult = await window.electronAPI.detectMainBranch(result.data.path);
|
||||||
|
if (mainBranchResult.success && mainBranchResult.data) {
|
||||||
|
await window.electronAPI.updateProjectSettings(project.id, {
|
||||||
|
mainBranch: mainBranchResult.data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Non-fatal - main branch can be set later in settings
|
||||||
|
}
|
||||||
|
}
|
||||||
onProjectAdded?.(project, true); // New projects always need init
|
onProjectAdded?.(project, true); // New projects always need init
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
Zap,
|
Zap,
|
||||||
Eye,
|
Eye,
|
||||||
@@ -10,19 +11,32 @@ import {
|
|||||||
Import,
|
Import,
|
||||||
Radio,
|
Radio,
|
||||||
Github,
|
Github,
|
||||||
RefreshCw
|
RefreshCw,
|
||||||
|
GitBranch
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
import { Input } from '../ui/input';
|
import { Input } from '../ui/input';
|
||||||
import { Label } from '../ui/label';
|
import { Label } from '../ui/label';
|
||||||
import { Switch } from '../ui/switch';
|
import { Switch } from '../ui/switch';
|
||||||
import { Separator } from '../ui/separator';
|
import { Separator } from '../ui/separator';
|
||||||
import type { ProjectEnvConfig, LinearSyncStatus, GitHubSyncStatus } from '../../../shared/types';
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue
|
||||||
|
} from '../ui/select';
|
||||||
|
import type { ProjectEnvConfig, LinearSyncStatus, GitHubSyncStatus, Project, ProjectSettings as ProjectSettingsType } from '../../../shared/types';
|
||||||
|
|
||||||
interface IntegrationSettingsProps {
|
interface IntegrationSettingsProps {
|
||||||
envConfig: ProjectEnvConfig | null;
|
envConfig: ProjectEnvConfig | null;
|
||||||
updateEnvConfig: (updates: Partial<ProjectEnvConfig>) => void;
|
updateEnvConfig: (updates: Partial<ProjectEnvConfig>) => void;
|
||||||
|
|
||||||
|
// Project settings for main branch
|
||||||
|
project: Project;
|
||||||
|
settings: ProjectSettingsType;
|
||||||
|
setSettings: React.Dispatch<React.SetStateAction<ProjectSettingsType>>;
|
||||||
|
|
||||||
// Linear state
|
// Linear state
|
||||||
showLinearKey: boolean;
|
showLinearKey: boolean;
|
||||||
setShowLinearKey: React.Dispatch<React.SetStateAction<boolean>>;
|
setShowLinearKey: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
@@ -44,6 +58,9 @@ interface IntegrationSettingsProps {
|
|||||||
export function IntegrationSettings({
|
export function IntegrationSettings({
|
||||||
envConfig,
|
envConfig,
|
||||||
updateEnvConfig,
|
updateEnvConfig,
|
||||||
|
project,
|
||||||
|
settings,
|
||||||
|
setSettings,
|
||||||
showLinearKey,
|
showLinearKey,
|
||||||
setShowLinearKey,
|
setShowLinearKey,
|
||||||
linearConnectionStatus,
|
linearConnectionStatus,
|
||||||
@@ -58,6 +75,38 @@ export function IntegrationSettings({
|
|||||||
githubExpanded,
|
githubExpanded,
|
||||||
onGitHubToggle
|
onGitHubToggle
|
||||||
}: IntegrationSettingsProps) {
|
}: IntegrationSettingsProps) {
|
||||||
|
// Branch selection state
|
||||||
|
const [branches, setBranches] = useState<string[]>([]);
|
||||||
|
const [isLoadingBranches, setIsLoadingBranches] = useState(false);
|
||||||
|
|
||||||
|
// Load branches when GitHub section expands
|
||||||
|
useEffect(() => {
|
||||||
|
if (githubExpanded && project.path) {
|
||||||
|
loadBranches();
|
||||||
|
}
|
||||||
|
}, [githubExpanded, project.path]);
|
||||||
|
|
||||||
|
const loadBranches = async () => {
|
||||||
|
setIsLoadingBranches(true);
|
||||||
|
try {
|
||||||
|
const result = await window.electronAPI.getGitBranches(project.path);
|
||||||
|
if (result.success && result.data) {
|
||||||
|
setBranches(result.data);
|
||||||
|
// Auto-detect main branch if not set
|
||||||
|
if (!settings.mainBranch) {
|
||||||
|
const detectResult = await window.electronAPI.detectMainBranch(project.path);
|
||||||
|
if (detectResult.success && detectResult.data) {
|
||||||
|
setSettings(prev => ({ ...prev, mainBranch: detectResult.data! }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load branches:', error);
|
||||||
|
} finally {
|
||||||
|
setIsLoadingBranches(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (!envConfig) return null;
|
if (!envConfig) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -385,6 +434,47 @@ export function IntegrationSettings({
|
|||||||
onCheckedChange={(checked) => updateEnvConfig({ githubAutoSync: checked })}
|
onCheckedChange={(checked) => updateEnvConfig({ githubAutoSync: checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
{/* Main Branch Selection */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<GitBranch className="h-4 w-4 text-info" />
|
||||||
|
<Label className="text-sm font-medium text-foreground">Main Branch</Label>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
The base branch for creating task worktrees. All new tasks will branch from here.
|
||||||
|
</p>
|
||||||
|
<Select
|
||||||
|
value={settings.mainBranch || ''}
|
||||||
|
onValueChange={(value) => setSettings(prev => ({ ...prev, mainBranch: value }))}
|
||||||
|
disabled={isLoadingBranches || branches.length === 0}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
{isLoadingBranches ? (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Loader2 className="h-3 w-3 animate-spin" />
|
||||||
|
<span>Loading branches...</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<SelectValue placeholder="Select main branch" />
|
||||||
|
)}
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{branches.map((branch) => (
|
||||||
|
<SelectItem key={branch} value={branch}>
|
||||||
|
{branch}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
{settings.mainBranch && (
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Tasks will be created on branches like <code className="px-1 bg-muted rounded">auto-claude/task-name</code> from <code className="px-1 bg-muted rounded">{settings.mainBranch}</code>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -118,6 +118,9 @@ export function ProjectSettings({ project, open, onOpenChange }: ProjectSettings
|
|||||||
<IntegrationSettings
|
<IntegrationSettings
|
||||||
envConfig={envConfig}
|
envConfig={envConfig}
|
||||||
updateEnvConfig={updateEnvConfig}
|
updateEnvConfig={updateEnvConfig}
|
||||||
|
project={project}
|
||||||
|
settings={settings}
|
||||||
|
setSettings={setSettings}
|
||||||
showLinearKey={showLinearKey}
|
showLinearKey={showLinearKey}
|
||||||
setShowLinearKey={setShowLinearKey}
|
setShowLinearKey={setShowLinearKey}
|
||||||
linearConnectionStatus={linearConnectionStatus}
|
linearConnectionStatus={linearConnectionStatus}
|
||||||
|
|||||||
@@ -68,5 +68,21 @@ export const projectMock = {
|
|||||||
listDirectory: async () => ({
|
listDirectory: async () => ({
|
||||||
success: true,
|
success: true,
|
||||||
data: []
|
data: []
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Git operations
|
||||||
|
getGitBranches: async () => ({
|
||||||
|
success: true,
|
||||||
|
data: ['main', 'develop', 'feature/test']
|
||||||
|
}),
|
||||||
|
|
||||||
|
getCurrentGitBranch: async () => ({
|
||||||
|
success: true,
|
||||||
|
data: 'main'
|
||||||
|
}),
|
||||||
|
|
||||||
|
detectMainBranch: async () => ({
|
||||||
|
success: true,
|
||||||
|
data: 'main'
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -246,5 +246,10 @@ export const IPC_CHANNELS = {
|
|||||||
INSIGHTS_ERROR: 'insights:error',
|
INSIGHTS_ERROR: 'insights:error',
|
||||||
|
|
||||||
// File explorer operations
|
// File explorer operations
|
||||||
FILE_EXPLORER_LIST: 'fileExplorer:list'
|
FILE_EXPLORER_LIST: 'fileExplorer:list',
|
||||||
|
|
||||||
|
// Git operations
|
||||||
|
GIT_GET_BRANCHES: 'git:getBranches',
|
||||||
|
GIT_GET_CURRENT_BRANCH: 'git:getCurrentBranch',
|
||||||
|
GIT_DETECT_MAIN_BRANCH: 'git:detectMainBranch'
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -459,6 +459,11 @@ export interface ElectronAPI {
|
|||||||
|
|
||||||
// File explorer operations
|
// File explorer operations
|
||||||
listDirectory: (dirPath: string) => Promise<IPCResult<FileNode[]>>;
|
listDirectory: (dirPath: string) => Promise<IPCResult<FileNode[]>>;
|
||||||
|
|
||||||
|
// Git operations
|
||||||
|
getGitBranches: (projectPath: string) => Promise<IPCResult<string[]>>;
|
||||||
|
getCurrentGitBranch: (projectPath: string) => Promise<IPCResult<string | null>>;
|
||||||
|
detectMainBranch: (projectPath: string) => Promise<IPCResult<string | null>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ export interface ProjectSettings {
|
|||||||
graphitiMcpEnabled: boolean;
|
graphitiMcpEnabled: boolean;
|
||||||
/** Graphiti MCP server URL (default: http://localhost:8000/mcp/) */
|
/** Graphiti MCP server URL (default: http://localhost:8000/mcp/) */
|
||||||
graphitiMcpUrl?: string;
|
graphitiMcpUrl?: string;
|
||||||
|
/** Main branch name for worktree creation (default: auto-detected or 'main') */
|
||||||
|
mainBranch?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NotificationSettings {
|
export interface NotificationSettings {
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ def handle_build_command(
|
|||||||
auto_continue: bool,
|
auto_continue: bool,
|
||||||
skip_qa: bool,
|
skip_qa: bool,
|
||||||
force_bypass_approval: bool,
|
force_bypass_approval: bool,
|
||||||
|
base_branch: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Handle the main build command.
|
Handle the main build command.
|
||||||
@@ -75,6 +76,7 @@ def handle_build_command(
|
|||||||
auto_continue: Auto-continue mode (non-interactive)
|
auto_continue: Auto-continue mode (non-interactive)
|
||||||
skip_qa: Skip automatic QA validation
|
skip_qa: Skip automatic QA validation
|
||||||
force_bypass_approval: Force bypass approval check
|
force_bypass_approval: Force bypass approval check
|
||||||
|
base_branch: Base branch for worktree creation (default: current branch)
|
||||||
"""
|
"""
|
||||||
# Lazy imports to avoid loading heavy modules
|
# Lazy imports to avoid loading heavy modules
|
||||||
from agent import run_autonomous_agent, sync_plan_to_source
|
from agent import run_autonomous_agent, sync_plan_to_source
|
||||||
@@ -182,7 +184,8 @@ def handle_build_command(
|
|||||||
source_spec_dir = spec_dir
|
source_spec_dir = spec_dir
|
||||||
|
|
||||||
working_dir, worktree_manager, localized_spec_dir = setup_workspace(
|
working_dir, worktree_manager, localized_spec_dir = setup_workspace(
|
||||||
project_dir, spec_dir.name, workspace_mode, source_spec_dir=spec_dir
|
project_dir, spec_dir.name, workspace_mode, source_spec_dir=spec_dir,
|
||||||
|
base_branch=base_branch
|
||||||
)
|
)
|
||||||
# Use the localized spec directory (inside worktree) for AI access
|
# Use the localized spec directory (inside worktree) for AI access
|
||||||
if localized_spec_dir:
|
if localized_spec_dir:
|
||||||
|
|||||||
@@ -229,6 +229,14 @@ Environment Variables:
|
|||||||
help="Skip approval check and start build anyway (for debugging)",
|
help="Skip approval check and start build anyway (for debugging)",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Base branch for worktree creation
|
||||||
|
parser.add_argument(
|
||||||
|
"--base-branch",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="Base branch for creating worktrees (default: auto-detect or current branch)",
|
||||||
|
)
|
||||||
|
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
@@ -366,6 +374,7 @@ def main() -> None:
|
|||||||
auto_continue=args.auto_continue,
|
auto_continue=args.auto_continue,
|
||||||
skip_qa=args.skip_qa,
|
skip_qa=args.skip_qa,
|
||||||
force_bypass_approval=args.force,
|
force_bypass_approval=args.force,
|
||||||
|
base_branch=args.base_branch,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ def setup_workspace(
|
|||||||
spec_name: str,
|
spec_name: str,
|
||||||
mode: WorkspaceMode,
|
mode: WorkspaceMode,
|
||||||
source_spec_dir: Path | None = None,
|
source_spec_dir: Path | None = None,
|
||||||
|
base_branch: str | None = None,
|
||||||
) -> tuple[Path, WorktreeManager | None, Path | None]:
|
) -> tuple[Path, WorktreeManager | None, Path | None]:
|
||||||
"""
|
"""
|
||||||
Set up the workspace based on user's choice.
|
Set up the workspace based on user's choice.
|
||||||
@@ -195,6 +196,7 @@ def setup_workspace(
|
|||||||
spec_name: Name of the spec being built (e.g., "001-feature-name")
|
spec_name: Name of the spec being built (e.g., "001-feature-name")
|
||||||
mode: The workspace mode to use
|
mode: The workspace mode to use
|
||||||
source_spec_dir: Optional source spec directory to copy to worktree
|
source_spec_dir: Optional source spec directory to copy to worktree
|
||||||
|
base_branch: Base branch for worktree creation (default: current branch)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple of (working_directory, worktree_manager or None, localized_spec_dir or None)
|
Tuple of (working_directory, worktree_manager or None, localized_spec_dir or None)
|
||||||
@@ -215,7 +217,7 @@ def setup_workspace(
|
|||||||
# Ensure timeline tracking hook is installed (once per session)
|
# Ensure timeline tracking hook is installed (once per session)
|
||||||
ensure_timeline_hook_installed(project_dir)
|
ensure_timeline_hook_installed(project_dir)
|
||||||
|
|
||||||
manager = WorktreeManager(project_dir)
|
manager = WorktreeManager(project_dir, base_branch=base_branch)
|
||||||
manager.setup()
|
manager.setup()
|
||||||
|
|
||||||
# Get or create worktree for THIS SPECIFIC SPEC
|
# Get or create worktree for THIS SPECIFIC SPEC
|
||||||
|
|||||||
Reference in New Issue
Block a user