( pathToResolve: string )
| 222 | * @returns The original project path, or the input if not a worktree, or null if resolution fails |
| 223 | */ |
| 224 | export function resolveProjectPathFromWorktree( |
| 225 | pathToResolve: string |
| 226 | ): string | null { |
| 227 | const worktreeMarker = path.join(".21st", "worktrees") |
| 228 | |
| 229 | // Normalize for cross-platform (handle both / and \ separators) |
| 230 | const normalizedPath = pathToResolve.replace(/\\/g, "/") |
| 231 | const normalizedMarker = worktreeMarker.replace(/\\/g, "/") |
| 232 | |
| 233 | if (!normalizedPath.includes(normalizedMarker)) { |
| 234 | // Not a worktree path, return as-is |
| 235 | return pathToResolve |
| 236 | } |
| 237 | |
| 238 | try { |
| 239 | // Extract segments from path structure |
| 240 | // Path format: /Users/.../.21st/worktrees/{projectSlug}/{worktreeFolder} |
| 241 | const worktreeBase = path.join(os.homedir(), ".21st", "worktrees") |
| 242 | const normalizedBase = worktreeBase.replace(/\\/g, "/") |
| 243 | const relativePath = normalizedPath |
| 244 | .replace(normalizedBase, "") |
| 245 | .replace(/^\//, "") |
| 246 | |
| 247 | const parts = relativePath.split("/") |
| 248 | if (parts.length < 1 || !parts[0]) { |
| 249 | return null |
| 250 | } |
| 251 | |
| 252 | const db = getDatabase() |
| 253 | |
| 254 | // Strategy 1: Legacy lookup - folder name is a projectId |
| 255 | const projectById = db |
| 256 | .select({ path: projects.path }) |
| 257 | .from(projects) |
| 258 | .where(eq(projects.id, parts[0])) |
| 259 | .get() |
| 260 | |
| 261 | if (projectById) { |
| 262 | return projectById.path |
| 263 | } |
| 264 | |
| 265 | // Strategy 2: New format - folder name is the project name. |
| 266 | // Look up via chats.worktreePath which stores the full path. |
| 267 | if (parts.length >= 2) { |
| 268 | const expectedWorktreePath = path.join(worktreeBase, parts[0], parts[1]) |
| 269 | const chat = db |
| 270 | .select({ projectId: chats.projectId }) |
| 271 | .from(chats) |
| 272 | .where(eq(chats.worktreePath, expectedWorktreePath)) |
| 273 | .get() |
| 274 | |
| 275 | if (chat) { |
| 276 | const project = db |
| 277 | .select({ path: projects.path }) |
| 278 | .from(projects) |
| 279 | .where(eq(projects.id, chat.projectId)) |
| 280 | .get() |
| 281 |
no test coverage detected