From 79620bccaa8bf72d97e78f680345196aaa63b6fc Mon Sep 17 00:00:00 2001 From: Kartikeya <176560021+karti-ai@users.noreply.github.com> Date: Sun, 28 Jun 2026 10:34:16 -0700 Subject: [PATCH] fix(collision): treat case/whitespace-variant identities as one person Vision reports a display name ("Karti") while the git watcher reports a handle ("karti"); the detectors compared engineerId case-sensitively, so the same human collided with themselves. Canonicalize identity at the decision point in both the file and research detectors, keeping the display-cased name for the card. Co-Authored-By: Claude Opus 4.8 --- backend/src/collision/detector.ts | 20 ++++++++++++++++++-- backend/src/collision/research.ts | 8 +++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/backend/src/collision/detector.ts b/backend/src/collision/detector.ts index 99837bb..e2297e9 100644 --- a/backend/src/collision/detector.ts +++ b/backend/src/collision/detector.ts @@ -19,6 +19,15 @@ function fileKey(raw?: string): string | undefined { return base.toLowerCase(); } +/** + * Canonicalize an engineer identity for case/whitespace-insensitive matching. + * Vision reports a display name ("Karti") while the git watcher reports a handle + * ("karti"); without this the same human collides with themselves. + */ +function canonicalName(raw: string): string { + return raw.trim().toLowerCase(); +} + interface Touch { engineerId: string; unpushed: boolean; @@ -64,8 +73,15 @@ export function detectCollisions( const out: Collision[] = []; for (const [, touches] of byFile) { - const engineers = [...new Set(touches.map((t) => t.engineerId))]; - if (engineers.length < 2) continue; // need two distinct people on one file + // Distinct PEOPLE, case/whitespace-insensitive — one display name per person. + // Vision "Karti" and git "karti" are the same human, not a collision. + const byPerson = new Map(); // canonical id -> display name + for (const t of touches) { + const id = canonicalName(t.engineerId); + if (id && !byPerson.has(id)) byPerson.set(id, t.engineerId); + } + if (byPerson.size < 2) continue; // need two distinct people on one file + const engineers = [...byPerson.values()]; const anyUnpushed = touches.some((t) => t.unpushed) || github.unpushed === true; if (!anyUnpushed) continue; // the crux GitHub alone cannot answer diff --git a/backend/src/collision/research.ts b/backend/src/collision/research.ts index ba86206..357fe00 100644 --- a/backend/src/collision/research.ts +++ b/backend/src/collision/research.ts @@ -24,6 +24,12 @@ function stripGitPrefix(raw: string): string { return raw.trim().replace(/^(\?\?|[MADRCU!]{1,2})\s+/, ''); } +/** Case/whitespace-insensitive identity so vision "Karti" and git "karti" are + * recognized as the same person and never flagged researching-vs-editing self. */ +function canonicalName(raw: string): string { + return raw.trim().toLowerCase(); +} + function fileStem(raw: string): string { const base = stripGitPrefix(raw).split(/[\\/]/).pop()?.trim().toLowerCase() ?? ''; return base.replace(/\.[^.]+$/, ''); @@ -104,7 +110,7 @@ export async function detectResearchOverlaps( const researchText = [topic, source].filter(Boolean).join(' '); for (const editor of editorFiles) { - if (editor.engineerId === researcher.engineerId) continue; + if (canonicalName(editor.engineerId) === canonicalName(researcher.engineerId)) continue; const stem = fileStem(editor.file); if (!stem) continue;