fix(project-name): use parent/worktree composite so observations don't cross worktrees

Revert of #1820 behavior. Each worktree now gets its own bucket:
- In a worktree, primary = `parent/worktree` (e.g. `claude-mem/dar-es-salaam`)
- In a main repo, primary = basename (unchanged)
- allProjects is always `[primary]` — strict isolation at query time

Includes a one-off maintenance script (scripts/worktree-remap.ts) that
retroactively reattributes past sessions to their worktree using path
signals in observations and user prompts. Two-rule inference keeps the
remap high-confidence:
  1. The worktree basename in the path matches the session's current
     plain project name (pre-#1820 era; trusted).
  2. Or all worktree path signals converge on a single (parent, worktree)
     across the session.
Ambiguous sessions are skipped.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2026-04-16 15:40:44 -07:00
parent 53622b59e9
commit 040729beef
5 changed files with 196 additions and 28 deletions
+7 -10
View File
@@ -97,7 +97,7 @@ describe('getProjectContext', () => {
expect(ctx.parent).toBeNull();
});
describe('worktree regression (#1081, #1500, #1819)', () => {
describe('worktree isolation', () => {
let tmp: string;
let mainRepo: string;
let worktreeCheckout: string;
@@ -125,21 +125,18 @@ describe('getProjectContext', () => {
rmSync(tmp, { recursive: true, force: true });
});
it('uses parent project name as primary when in a worktree', () => {
it('uses parent/worktree composite as primary when in a worktree', () => {
const ctx = getProjectContext(worktreeCheckout);
expect(ctx.isWorktree).toBe(true);
expect(ctx.primary).toBe('main-repo');
expect(ctx.primary).toBe('main-repo/my-worktree');
expect(ctx.parent).toBe('main-repo');
expect(ctx.allProjects).toEqual(['main-repo', 'my-worktree']);
expect(ctx.allProjects).toEqual(['main-repo/my-worktree']);
});
it('write-path call sites resolve to parent project in worktrees', () => {
// Mirrors the pattern used by session-init.ts and SessionRoutes.ts:
// const project = getProjectContext(cwd).primary;
// This must resolve to the parent repo, not the worktree name,
// so observations are stored under the correct project.
it('write-path call sites resolve to composite name in worktrees', () => {
const project = getProjectContext(worktreeCheckout).primary;
expect(project).toBe('main-repo');
expect(project).toBe('main-repo/my-worktree');
expect(project).not.toBe('main-repo');
expect(project).not.toBe('my-worktree');
});
});