feat(server): self-identification tools

This commit is contained in:
Mohamed Boudra
2026-01-22 14:01:57 +07:00
parent cc2bdbf01f
commit ef9c538842
11 changed files with 557 additions and 175 deletions
+55 -18
View File
@@ -35,6 +35,13 @@ export interface PaseoWorktreeInfo {
head?: string;
}
export type PaseoWorktreeOwnership = {
allowed: boolean;
repoRoot?: string;
worktreeRoot?: string;
worktreePath?: string;
};
interface CreateWorktreeOptions {
branchName: string;
cwd: string;
@@ -99,26 +106,26 @@ export async function detectRepoInfo(cwd: string): Promise<RepoInfo> {
}
}
// Check if we're in a normal git repository
const gitDirPath = join(cwd, ".git");
if (existsSync(gitDirPath)) {
try {
const { stdout } = await execAsync("git rev-parse --show-toplevel", {
cwd,
env: READ_ONLY_GIT_ENV,
});
const repoRoot = stdout.trim();
return {
type: "normal",
path: repoRoot,
name: basename(repoRoot),
};
} catch (error) {
throw new Error("Failed to determine git repository root");
// Fallback: allow running from any subdirectory inside a git checkout/worktree.
// Use git's common dir (shared .git) to find the repo root even when cwd has no .git entry.
try {
const { stdout } = await execAsync(
"git rev-parse --path-format=absolute --git-common-dir",
{ cwd, env: READ_ONLY_GIT_ENV }
);
const commonDir = stdout.trim();
if (!commonDir) {
throw new Error("git-common-dir was empty");
}
const repoRoot = basename(commonDir) === ".git" ? dirname(commonDir) : dirname(commonDir);
return {
type: "normal",
path: repoRoot,
name: basename(repoRoot),
};
} catch {
throw new Error("Not in a git repository");
}
throw new Error("Not in a git repository");
}
/**
@@ -195,6 +202,36 @@ function getPaseoWorktreesRoot(repoRoot: string): string {
return join(repoRoot, ".paseo", "worktrees");
}
export async function isPaseoOwnedWorktreeCwd(
cwd: string
): Promise<PaseoWorktreeOwnership> {
const repoInfo = await detectRepoInfo(cwd);
const worktreesRoot = getPaseoWorktreesRoot(repoInfo.path);
const resolvedRoot = resolve(worktreesRoot) + sep;
const resolvedCwd = resolve(cwd);
if (!resolvedCwd.startsWith(resolvedRoot)) {
return {
allowed: false,
repoRoot: repoInfo.path,
worktreeRoot: worktreesRoot,
worktreePath: resolvedCwd,
};
}
const worktrees = await listPaseoWorktrees({ cwd: repoInfo.path });
const allowed = worktrees.some((entry) => {
const worktreePath = resolve(entry.path);
return resolvedCwd === worktreePath || resolvedCwd.startsWith(worktreePath + sep);
});
return {
allowed,
repoRoot: repoInfo.path,
worktreeRoot: worktreesRoot,
worktreePath: resolvedCwd,
};
}
function ensurePaseoIgnoredForRepo(repoInfo: RepoInfo): {
updated: boolean;
skipped: boolean;