Move AI-merge clean-room worktrees into the configured worktrees directory while preserving legacy cleanup. - Resolve AI-merge roots through the configured worktrees directory using a hidden .ai-merge container. - Keep local git excludes and stale cleanup compatible with legacy .fusion/ai-merge and tmpdir locations. - Exclude the .ai-merge container from worktree pool and self-healing one-level sweeps. - Add regression coverage for path resolution, cleanup, orphan reap, and cap enforcement behavior. - Update architecture and agent guidance for the relocated clean-room root. Files changed: AGENTS.md | 2 +- docs/architecture.md | 4 +- .../engine/src/__tests__/merger-ai-cleanup.test.ts | 50 +++++++++---- .../__tests__/self-healing-tempdir-sweep.test.ts | 87 +++++++++++++++++++--- .../engine/src/__tests__/worktree-paths.test.ts | 24 ++++++ .../engine/src/__tests__/worktree-pool.test.ts | 55 ++++++++++++++ packages/engine/src/merger-ai.ts | 27 +++++-- packages/engine/src/self-healing.ts | 25 +++---- packages/engine/src/worktree-paths.ts | 17 +++++ packages/engine/src/worktree-pool.ts | 10 +-- 10 files changed, 247 insertions(+), 54 deletions(-) Fusion-Task-Id: FN-6295 Fusion-Task-Lineage: d95d32c2-3253-4964-b68a-252b4cb65109
73 lines
2.4 KiB
TypeScript
73 lines
2.4 KiB
TypeScript
import { homedir } from "node:os";
|
|
import { basename, isAbsolute, join, relative, resolve } from "node:path";
|
|
import type { Settings } from "@fusion/core";
|
|
import type { WorktreeBackendKind } from "./worktree-backend.js";
|
|
import { canonicalizePath } from "./worktree-pool.js";
|
|
|
|
export const AI_MERGE_DIRNAME = ".ai-merge";
|
|
|
|
export function isAiMergeContainerDir(name: string): boolean {
|
|
return name === AI_MERGE_DIRNAME;
|
|
}
|
|
|
|
export function resolveAiMergeRootPath(
|
|
rootDir: string,
|
|
settings: Pick<Settings, "worktreesDir"> | undefined,
|
|
): string {
|
|
return join(resolveWorktreesDir(rootDir, settings), AI_MERGE_DIRNAME);
|
|
}
|
|
|
|
export function resolveLegacyAiMergeRootPath(rootDir: string): string {
|
|
return join(rootDir, ".fusion", "ai-merge");
|
|
}
|
|
|
|
export function resolveWorktreesDir(
|
|
rootDir: string,
|
|
settings: Pick<Settings, "worktreesDir"> | undefined,
|
|
): string {
|
|
const configured = settings?.worktreesDir;
|
|
if (!configured) {
|
|
return join(rootDir, ".worktrees");
|
|
}
|
|
|
|
const expandedHome = configured.replace(/^~(?=$|[\\/])/, homedir());
|
|
const expandedRepo = expandedHome.replaceAll("{repo}", basename(rootDir));
|
|
return resolve(rootDir, expandedRepo);
|
|
}
|
|
|
|
export function resolveTaskWorktreePath(
|
|
rootDir: string,
|
|
settings: Pick<Settings, "worktreesDir"> | undefined,
|
|
worktreeName: string,
|
|
): string {
|
|
return join(resolveWorktreesDir(rootDir, settings), worktreeName);
|
|
}
|
|
|
|
// Structural backend input avoids importing the full WorktreeBackend interface here.
|
|
export async function resolveTaskWorktreePathForBackend(
|
|
rootDir: string,
|
|
worktreeName: string,
|
|
settings: Pick<Settings, "worktreesDir"> | undefined,
|
|
backend: {
|
|
kind: WorktreeBackendKind;
|
|
resolveWorktreePath?: (input: { rootDir: string; worktreeName: string; branch: string }) => Promise<string>;
|
|
},
|
|
branch: string,
|
|
): Promise<string> {
|
|
if (backend.kind === "worktrunk" && backend.resolveWorktreePath) {
|
|
return backend.resolveWorktreePath({ rootDir, worktreeName, branch });
|
|
}
|
|
return resolveTaskWorktreePath(rootDir, settings, worktreeName);
|
|
}
|
|
|
|
export function isInsideConfiguredWorktreesDir(
|
|
rootDir: string,
|
|
settings: Pick<Settings, "worktreesDir"> | undefined,
|
|
candidate: string,
|
|
): boolean {
|
|
const worktreesDir = canonicalizePath(resolveWorktreesDir(rootDir, settings));
|
|
const target = canonicalizePath(candidate);
|
|
const rel = relative(worktreesDir, target);
|
|
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
}
|