feat: Auto-branch initiative system with per-project default branches

Planning tasks (research, discuss, plan, detail, refine) now run on
the project's defaultBranch instead of hardcoded 'main'. Execution
tasks (execute, verify, merge, review) auto-generate an initiative
branch (cw/<slug>) on first dispatch. Branch configuration removed
from initiative creation — it's now fully automatic.

- Add PLANNING_CATEGORIES/EXECUTION_CATEGORIES to branch-naming
- Dispatch manager splits logic by task category
- ProcessManager uses per-project defaultBranch fallback
- Phase dispatch uses project.defaultBranch for ensureBranch base
- Remove mergeTarget from createInitiative input
- Rename updateInitiativeMergeConfig → updateInitiativeConfig
- Add defaultBranch field to registerProject + UI
- Rename mergeTarget → branch across all frontend components
This commit is contained in:
Lukas May
2026-02-10 10:53:35 +01:00
parent 0407f05332
commit ca548c1eaa
13 changed files with 93 additions and 58 deletions

View File

@@ -49,7 +49,7 @@ describe('writeInputFiles', () => {
name: 'Test Initiative',
status: 'active',
mergeRequiresApproval: true,
mergeTarget: 'main',
branch: 'cw/test-initiative',
executionMode: 'review_per_phase',
createdAt: new Date('2026-01-01'),
updatedAt: new Date('2026-01-02'),

View File

@@ -125,7 +125,7 @@ export function writeInputFiles(options: WriteInputFilesOptions): void {
name: ini.name,
status: ini.status,
mergeRequiresApproval: ini.mergeRequiresApproval,
mergeTarget: ini.mergeTarget,
branch: ini.branch,
},
'',
);

View File

@@ -222,7 +222,7 @@ export class MultiProviderAgentManager implements AgentManager {
let agentCwd: string;
if (initiativeId) {
log.debug({ alias, initiativeId, baseBranch, branchName }, 'creating initiative-based worktrees');
agentCwd = await this.processManager.createProjectWorktrees(alias, initiativeId, baseBranch ?? 'main', branchName);
agentCwd = await this.processManager.createProjectWorktrees(alias, initiativeId, baseBranch, branchName);
// Log projects linked to the initiative
const projects = await this.projectRepository.findProjectsByInitiativeId(initiativeId);

View File

@@ -54,7 +54,7 @@ export class ProcessManager {
async createProjectWorktrees(
alias: string,
initiativeId: string,
baseBranch: string = 'main',
baseBranch?: string,
branchName?: string,
): Promise<string> {
const projects = await this.projectRepository.findProjectsByInitiativeId(initiativeId);
@@ -78,7 +78,8 @@ export class ProcessManager {
for (const project of projects) {
const clonePath = await ensureProjectClone(project, this.workspaceRoot);
const worktreeManager = new SimpleGitWorktreeManager(clonePath, undefined, agentWorkdir);
const worktree = await worktreeManager.create(project.name, branchName ?? `agent/${alias}`, baseBranch);
const effectiveBaseBranch = baseBranch ?? project.defaultBranch;
const worktree = await worktreeManager.create(project.name, branchName ?? `agent/${alias}`, effectiveBaseBranch);
const worktreePath = worktree.path;
const pathExists = existsSync(worktreePath);