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 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,15 @@ function fileKey(raw?: string): string | undefined {
|
|||||||
return base.toLowerCase();
|
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 {
|
interface Touch {
|
||||||
engineerId: string;
|
engineerId: string;
|
||||||
unpushed: boolean;
|
unpushed: boolean;
|
||||||
@@ -64,8 +73,15 @@ export function detectCollisions(
|
|||||||
|
|
||||||
const out: Collision[] = [];
|
const out: Collision[] = [];
|
||||||
for (const [, touches] of byFile) {
|
for (const [, touches] of byFile) {
|
||||||
const engineers = [...new Set(touches.map((t) => t.engineerId))];
|
// Distinct PEOPLE, case/whitespace-insensitive — one display name per person.
|
||||||
if (engineers.length < 2) continue; // need two distinct people on one file
|
// Vision "Karti" and git "karti" are the same human, not a collision.
|
||||||
|
const byPerson = new Map<string, string>(); // 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;
|
const anyUnpushed = touches.some((t) => t.unpushed) || github.unpushed === true;
|
||||||
if (!anyUnpushed) continue; // the crux GitHub alone cannot answer
|
if (!anyUnpushed) continue; // the crux GitHub alone cannot answer
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ function stripGitPrefix(raw: string): string {
|
|||||||
return raw.trim().replace(/^(\?\?|[MADRCU!]{1,2})\s+/, '');
|
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 {
|
function fileStem(raw: string): string {
|
||||||
const base = stripGitPrefix(raw).split(/[\\/]/).pop()?.trim().toLowerCase() ?? '';
|
const base = stripGitPrefix(raw).split(/[\\/]/).pop()?.trim().toLowerCase() ?? '';
|
||||||
return base.replace(/\.[^.]+$/, '');
|
return base.replace(/\.[^.]+$/, '');
|
||||||
@@ -104,7 +110,7 @@ export async function detectResearchOverlaps(
|
|||||||
const researchText = [topic, source].filter(Boolean).join(' ');
|
const researchText = [topic, source].filter(Boolean).join(' ');
|
||||||
|
|
||||||
for (const editor of editorFiles) {
|
for (const editor of editorFiles) {
|
||||||
if (editor.engineerId === researcher.engineerId) continue;
|
if (canonicalName(editor.engineerId) === canonicalName(researcher.engineerId)) continue;
|
||||||
|
|
||||||
const stem = fileStem(editor.file);
|
const stem = fileStem(editor.file);
|
||||||
if (!stem) continue;
|
if (!stem) continue;
|
||||||
|
|||||||
Reference in New Issue
Block a user