chore: merge main into cw/small-change-flow
Integrates main branch changes (headquarters dashboard, task retry count, agent prompt persistence, remote sync improvements) with the initiative's errand agent feature. Both features coexist in the merged result. Key resolutions: - Schema: take main's errands table (nullable projectId, no conflictFiles, with errandsRelations); migrate to 0035_faulty_human_fly - Router: keep both errandProcedures and headquartersProcedures - Errand prompt: take main's simpler version (no question-asking flow) - Manager: take main's status check (running|idle only, no waiting_for_input) - Tests: update to match removed conflictFiles field and undefined vs null
This commit is contained in:
@@ -61,11 +61,30 @@ export class SimpleGitWorktreeManager implements WorktreeManager {
|
||||
const worktreePath = path.join(this.worktreesDir, id);
|
||||
log.info({ id, branch, baseBranch }, 'creating worktree');
|
||||
|
||||
// Safety: never force-reset a branch to its own base — this would nuke
|
||||
// shared branches like the initiative branch if passed as both branch and baseBranch.
|
||||
if (branch === baseBranch) {
|
||||
throw new Error(`Worktree branch and baseBranch are the same (${branch}). Use a unique branch name.`);
|
||||
}
|
||||
|
||||
// Create worktree — reuse existing branch or create new one
|
||||
const branchExists = await this.branchExists(branch);
|
||||
if (branchExists) {
|
||||
// Branch exists from a previous run — reset it to baseBranch and check it out
|
||||
await this.git.raw(['branch', '-f', branch, baseBranch]);
|
||||
// Branch exists from a previous run. Check if it has commits beyond baseBranch
|
||||
// before resetting — a previous agent may have done real work on this branch.
|
||||
try {
|
||||
const aheadCount = await this.git.raw(['rev-list', '--count', `${baseBranch}..${branch}`]);
|
||||
if (parseInt(aheadCount.trim(), 10) > 0) {
|
||||
log.warn({ branch, baseBranch, aheadBy: aheadCount.trim() }, 'branch has commits beyond base, preserving');
|
||||
} else {
|
||||
await this.git.raw(['branch', '-f', branch, baseBranch]);
|
||||
}
|
||||
} catch {
|
||||
// If rev-list fails (e.g. baseBranch doesn't exist yet), fall back to reset
|
||||
await this.git.raw(['branch', '-f', branch, baseBranch]);
|
||||
}
|
||||
// Prune stale worktree references before adding new one
|
||||
await this.git.raw(['worktree', 'prune']);
|
||||
await this.git.raw(['worktree', 'add', worktreePath, branch]);
|
||||
} else {
|
||||
// git worktree add -b <branch> <path> <base-branch>
|
||||
@@ -140,8 +159,14 @@ export class SimpleGitWorktreeManager implements WorktreeManager {
|
||||
* Finds worktree by matching path ending with id.
|
||||
*/
|
||||
async get(id: string): Promise<Worktree | null> {
|
||||
const expectedSuffix = path.join(path.basename(this.worktreesDir), id);
|
||||
const worktrees = await this.list();
|
||||
return worktrees.find((wt) => wt.path.endsWith(id)) ?? null;
|
||||
// Match on the worktreesDir + id suffix to avoid cross-agent collisions.
|
||||
// Multiple agents may have worktrees ending with the same project name
|
||||
// (e.g., ".../agent-A/codewalk-district" vs ".../agent-B/codewalk-district").
|
||||
// We match on basename(worktreesDir)/id to handle symlink differences
|
||||
// (e.g., macOS /var → /private/var) while still being unambiguous.
|
||||
return worktrees.find((wt) => wt.path.endsWith(expectedSuffix)) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user