/** * SubprojectRegistry - Maps composite project IDs to their split data. * * When multiple sessions in the same encoded directory have different `cwd` values, * they are split into separate "subprojects". Each subproject gets a composite ID * of the form `{encodedDir}::{sha256(cwd).slice(0,8)}`. * * This singleton registry tracks: * - Which base directory a composite ID maps to * - Which cwd each subproject represents * - Which session IDs belong to each subproject */ import * as crypto from 'crypto'; interface SubprojectEntry { baseDir: string; cwd: string; sessionIds: Set; } class SubprojectRegistryImpl { private readonly entries = new Map(); /** * Register a subproject and return its composite ID. * * @param baseDir - The encoded directory name (e.g., "-Users-name-project") * @param cwd - The actual working directory for this subproject * @param sessionIds - Session IDs belonging to this subproject * @returns Composite ID in the form `{baseDir}::{hash}` */ register(baseDir: string, cwd: string, sessionIds: string[]): string { const hash = crypto.createHash('sha256').update(cwd).digest('hex').slice(0, 8); const compositeId = `${baseDir}::${hash}`; this.entries.set(compositeId, { baseDir, cwd, sessionIds: new Set(sessionIds), }); return compositeId; } /** * Extract the base directory from any project ID (composite or plain). * For composite IDs (`{encoded}::{hash}`), returns the encoded part. * For plain IDs, returns the ID as-is. */ getBaseDir(projectId: string): string { const sep = projectId.indexOf('::'); if (sep !== -1) { return projectId.slice(0, sep); } return projectId; } /** * Check if a project ID is a composite (split) ID. */ isComposite(projectId: string): boolean { return projectId.includes('::'); } /** * Get the session ID filter set for a composite project ID. * Returns null for plain (non-composite) IDs. */ getSessionFilter(projectId: string): Set | null { const entry = this.entries.get(projectId); return entry?.sessionIds ?? null; } /** * Get the cwd for a composite project ID. * Returns null for plain (non-composite) IDs. */ getCwd(projectId: string): string | null { const entry = this.entries.get(projectId); return entry?.cwd ?? null; } /** * Get the full entry for a composite project ID. */ getEntry(projectId: string): SubprojectEntry | undefined { return this.entries.get(projectId); } /** * Clear all registered subprojects. Called at the start of a full re-scan. */ clear(): void { this.entries.clear(); } } /** Module-level singleton */ export const subprojectRegistry = new SubprojectRegistryImpl();