fix: use parent project name for worktree observation writes (#1820)

* fix: use parent project name for worktree observation writes (#1819)

Observations and sessions from git worktrees were stored under
basename(cwd) instead of the parent repo name because write paths
called getProjectName() (not worktree-aware) instead of
getProjectContext() (worktree-aware). This is the same bug as
#1081, #1317, and #1500 — it regressed because the two functions
coexist and new code reached for the simpler one.

Fix: getProjectContext() now returns parentProjectName as primary
when in a worktree, and all four write-path call sites now use
getProjectContext().primary instead of getProjectName().

Includes regression test that creates a real worktree directory
structure and asserts primary === parentProjectName.

* fix: address review nitpicks — allProjects fallback, JSDoc, write-path test

- ContextBuilder: default projects to context.allProjects for legacy
  worktree-labeled record compatibility
- ProjectContext: clarify JSDoc that primary is canonical (parent repo
  in worktrees)
- Tests: add write-path regression test mirroring session-init/SessionRoutes
  pattern; refactor worktree fixture into beforeAll/afterAll

* refactor(project-name): rename local to cwdProjectName and dedupe allProjects

Addresses final CodeRabbit nitpick: disambiguates the local variable
from the returned `primary` field, and dedupes allProjects via Set
in case parent and cwd resolve to the same name.

---------

Co-authored-by: Ethan Hurst <ethan.hurst@outlook.com.au>
This commit is contained in:
Ethan
2026-04-15 17:58:14 +10:00
committed by GitHub
parent 3d92684e04
commit 16a0737dfc
6 changed files with 69 additions and 19 deletions
+10 -8
View File
@@ -58,13 +58,13 @@ export function getProjectName(cwd: string | null | undefined): string {
* Project context with worktree awareness
*/
export interface ProjectContext {
/** The current project name (worktree or main repo) */
/** Canonical project name for writes/queries (parent repo in worktrees) */
primary: string;
/** Parent project name if in a worktree, null otherwise */
parent: string | null;
/** True if currently in a worktree */
isWorktree: boolean;
/** All projects to query: [primary] for main repo, [parent, primary] for worktree */
/** All projects to query: [primary] for main repo, [parentRepo, worktreeName] for worktree */
allProjects: string[];
}
@@ -78,24 +78,26 @@ export interface ProjectContext {
* @returns ProjectContext with worktree info
*/
export function getProjectContext(cwd: string | null | undefined): ProjectContext {
const primary = getProjectName(cwd);
const cwdProjectName = getProjectName(cwd);
if (!cwd) {
return { primary, parent: null, isWorktree: false, allProjects: [primary] };
return { primary: cwdProjectName, parent: null, isWorktree: false, allProjects: [cwdProjectName] };
}
const expandedCwd = expandTilde(cwd);
const worktreeInfo = detectWorktree(expandedCwd);
if (worktreeInfo.isWorktree && worktreeInfo.parentProjectName) {
// In a worktree: include parent first for chronological ordering
// In a worktree: use parent project name as primary so observations
// are stored under the same project as the main repo (#1081, #1500, #1819)
const allProjects = Array.from(new Set([worktreeInfo.parentProjectName, cwdProjectName]));
return {
primary,
primary: worktreeInfo.parentProjectName,
parent: worktreeInfo.parentProjectName,
isWorktree: true,
allProjects: [worktreeInfo.parentProjectName, primary]
allProjects
};
}
return { primary, parent: null, isWorktree: false, allProjects: [primary] };
return { primary: cwdProjectName, parent: null, isWorktree: false, allProjects: [cwdProjectName] };
}