worktree isolation
设计理念
问题
解决方案
主工作区
├─→ worktree-1 (Agent 1)
├─→ worktree-2 (Agent 2)
└─→ worktree-3 (Agent 3)主工作区
├─→ worktree-1 (Agent 1)
├─→ worktree-2 (Agent 2)
└─→ worktree-3 (Agent 3)# 创建 worktree
git worktree add ../my-worktree
# 列出所有 worktree
git worktree list
# 删除 worktree
git worktree remove ../my-worktreeasync function createWorktree(
basePath: string,
branch?: string
): Promise<string> {
// 1. 生成唯一路径
const worktreePath = path.join(
basePath,
'.git',
'worktrees',
`agent-${Date.now()}-${randomId()}`
);
// 2. 创建 worktree
if (branch) {
await exec(`git worktree add -b ${branch} ${worktreePath}`);
} else {
await exec(`git worktree add ${worktreePath}`);
}
// 3. 复制配置文件
await copyConfig(basePath, worktreePath);
return worktreePath;
}async function copyConfig(
from: string,
to: string
): Promise<void> {
const configFiles = [
'.kiro/config.json',
'.env',
'package.json',
'tsconfig.json',
];
for (const file of configFiles) {
const source = path.join(from, file);
const dest = path.join(to, file);
if (await fs.pathExists(source)) {
await fs.copy(source, dest);
}
}
}async function cleanupWorktree(
worktreePath: string
): Promise<void> {
try {
// 1. 提交未保存的更改
await exec('git add -A', { cwd: worktreePath });
await exec('git commit -m "Agent work"', { cwd: worktreePath })
.catch(() => {}); // 忽略"无更改"错误
// 2. 删除 worktree
await exec(`git worktree remove ${worktreePath} --force`);
} catch (error) {
console.error('Failed to cleanup worktree:', error);
}
}async function mergeWorktree(
worktreePath: string,
basePath: string
): Promise<MergeResult> {
// 1. 获取 worktree 的分支名
const branch = await getBranchName(worktreePath);
// 2. 切换到主分支
await exec('git checkout main', { cwd: basePath });
// 3. 尝试合并
try {
await exec(`git merge ${branch}`, { cwd: basePath });
return { success: true };
} catch (error) {
// 4. 处理冲突
const conflicts = await getConflicts(basePath);
return {
success: false,
conflicts: conflicts,
};
}
}async function resolveConflicts(
conflicts: Conflict[],
strategy: 'ours' | 'theirs' | 'manual'
): Promise<void> {
for (const conflict of conflicts) {
if (strategy === 'ours') {
// 保留主分支的版本
await exec(`git checkout --ours ${conflict.file}`);
} else if (strategy === 'theirs') {
// 保留 worktree 的版本
await exec(`git checkout --theirs ${conflict.file}`);
} else {
// 手动解决
await manualResolve(conflict);
}
await exec(`git add ${conflict.file}`);
}
await exec('git commit -m "Resolve conflicts"');
}async function manualResolve(conflict: Conflict): Promise<void> {
// 1. 读取冲突文件
const content = await fs.readFile(conflict.file, 'utf-8');
// 2. 使用 AI 解决冲突
const resolved = await resolveWithAI(content, conflict);
// 3. 写回文件
await fs.writeFile(conflict.file, resolved);
}
async function resolveWithAI(
content: string,
conflict: Conflict
): Promise<string> {
const prompt = `
Resolve this Git conflict:
${content}
Context:
- Main branch: ${conflict.oursDescription}
- Agent branch: ${conflict.theirsDescription}
Return the resolved content without conflict markers.
`;
const response = await query([{ role: 'user', content: prompt }]);
return response;
}interface WorktreeStatus {
path: string;
branch: string;
hasChanges: boolean;
ahead: number; // 领先主分支的提交数
behind: number; // 落后主分支的提交数
}
async function getWorktreeStatus(
worktreePath: string
): Promise<WorktreeStatus> {
const branch = await getBranchName(worktreePath);
const status = await exec('git status --porcelain', { cwd: worktreePath });
const hasChanges = status.stdout.trim().length > 0;
// 获取与主分支的差异
const diff = await exec(
'git rev-list --left-right --count main...HEAD',
{ cwd: worktreePath }
);
const [behind, ahead] = diff.stdout.trim().split('\t').map(Number);
return {
path: worktreePath,
branch,
hasChanges,
ahead,
behind,
};
}async function syncWorktree(
worktreePath: string
): Promise<void> {
// 1. 提交本地更改
await exec('git add -A', { cwd: worktreePath });
await exec('git commit -m "WIP"', { cwd: worktreePath })
.catch(() => {});
// 2. 拉取主分支更新
await exec('git fetch origin main', { cwd: worktreePath });
// 3. 变基到最新
try {
await exec('git rebase origin/main', { cwd: worktreePath });
} catch {
// 变基失败,中止并报告
await exec('git rebase --abort', { cwd: worktreePath });
throw new Error('Rebase failed, manual intervention required');
}
}class WorktreePool {
private available: string[] = [];
private inUse = new Map<string, AgentInfo>();
async acquire(): Promise<string> {
if (this.available.length > 0) {
const worktree = this.available.pop();
return worktree;
}
// 创建新的 worktree
return await createWorktree();
}
async release(worktree: string): Promise<void> {
// 清理并回收
await cleanWorktree(worktree);
this.available.push(worktree);
}
async cleanup(): Promise<void> {
// 清理所有 worktree
for (const worktree of this.available) {
await cleanupWorktree(worktree);
}
this.available = [];
}
}