From 2006096cdadffe978a05d7dc6c4d011d096210bf Mon Sep 17 00:00:00 2001 From: sb-iam <59984144+sb-iam@users.noreply.github.com> Date: Sat, 27 Jun 2026 21:35:14 -0700 Subject: [PATCH] feat(graph): demo-ready materializer + per-pod Team memory action MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Materializer (live.ts) โ€” cut demo-pod from 110 -> 22 nodes: - cap to 8 recent collisions; collapse repeats by memorySignature - collapse interventions to one (most recent) per collision - filter junk 'files' (URLs, env vars, browser/app names, scratch/test) to real source paths only - prune test-artifact engineers (a/b/verify/codex-check/-testrepo) + any node orphaned by that - collisions referenced by accepted outcomes bypass the cap so the learned_from money path never drops; risk-paths metric counts distinct signatures UI: per-pod 'Team memory' action on each PodCard's menu (replaces the header button that always opened pods[0]). Verified against live demo-pod data (joins connect); typechecks clean. Co-Authored-By: Claude Opus 4.8 --- backend/src/graph/live.ts | 118 ++++++++++++++++++++++++---- frontend/src/App.tsx | 5 +- frontend/src/components/PodCard.tsx | 15 +++- 3 files changed, 117 insertions(+), 21 deletions(-) diff --git a/backend/src/graph/live.ts b/backend/src/graph/live.ts index e4cf481..0eadcd3 100644 --- a/backend/src/graph/live.ts +++ b/backend/src/graph/live.ts @@ -35,9 +35,24 @@ export function normalizeFile(f: string): string { return f .trim() .replace(/^["']|["']$/g, '') + .replace(/^[ACDMRTU?!]{1,2}\s+/, '') .replace(/^\.\//, ''); } +const MAX_COLLISIONS = 8; + +/** Reject "file" values that aren't real source paths โ€” vision/git noise such as + * URLs, env vars, browser/app names, and scratch/test artifacts. */ +const FILE_NOISE = + /(:\/\/|^[#~]|\s|\.env\b|\btett\b|test-change|demo-scratch|podman-test|scratch|sslip)/i; +export function isFilePath(f: string): boolean { + if (!f || FILE_NOISE.test(f)) return false; + return /\.[a-z0-9]{1,6}$/i.test(f); // must end in a real file extension +} + +/** Engineer names that are test/verification artifacts, not real teammates. */ +const ENGINEER_NOISE = /(^verify\b|^.$|testrepo|-?check\b|\d{4,})/i; + const STATUS_RANK: Record = { stable: 0, active: 1, @@ -166,32 +181,67 @@ export async function materializePodGraph(podId: string): Promise(); + for (const out of outcomeDocs) { + if (!out.accepted || !out.wasRealCollision) continue; + if (out.collisionId) priorityCol.add(out.collisionId); + const iv = interventionDocs.find((i) => i.id === out.interventionId); + if (iv?.collisionId) priorityCol.add(iv.collisionId); + } + + // 3. Collisions: collapse repeats by signature, keep the most recent, cap to + // MAX_COLLISIONS, skip junk-file collisions. `collisionById` keeps every doc + // (for the outcome join); `colNodeFor` maps each collisionId to its surviving + // collision node (or null when collapsed / capped / filtered out). const collisionById = new Map(); + const colNodeFor = new Map(); + const sigToNode = new Map(); + let distinctCollisions = 0; for (const col of collisionDocs) { collisionById.set(col.id, col); + const file = normalizeFile(col.file); + const sig = + (col as { memorySignature?: string }).memorySignature ?? `${file}#${col.symbol ?? ''}`; + const existing = sigToNode.get(sig); + if (existing) { + colNodeFor.set(col.id, existing); + continue; + } + if (!isFilePath(file)) { + colNodeFor.set(col.id, null); + continue; + } + const isPriority = priorityCol.has(col.id); + if (!isPriority && distinctCollisions >= MAX_COLLISIONS) { + colNodeFor.set(col.id, null); + continue; + } const cNode = upsertNode(b, 'collision', col.id, { - label: col.symbol ? `${col.file}#${col.symbol}` : col.file, + label: col.symbol ? `${file}#${col.symbol}` : file, status: 'risk', weight: SEVERITY_WEIGHT[col.severity] ?? 0.7, - summary: `${col.engineers.join(' + ')} on ${col.file}${ + summary: `${col.engineers.join(' + ')} on ${file}${ (col as { memorySignature?: string }).memorySignature ? ' ยท seen before' : '' }`, }); - const file = normalizeFile(col.file); const fNode = upsertNode(b, 'file', file, { label: file, status: 'risk' }); upsertEdge(b, fNode, cNode, 'touches', 'hot', 0.6); for (const name of col.engineers) { const eng = upsertNode(b, 'engineer', name, { label: name }); upsertEdge(b, eng, cNode, 'collides', 'in', SEVERITY_WEIGHT[col.severity] ?? 0.7); } + sigToNode.set(sig, cNode); + colNodeFor.set(col.id, cNode); + if (!isPriority) distinctCollisions++; } // 4. Git truth (engineer_states): mark unpushed work and confirm editing on @@ -212,10 +262,16 @@ export async function materializePodGraph(podId: string): Promise(); - for (const iv of interventionDocs) { + const ivNodeForCol = new Map(); + const sortedIvs = [...interventionDocs].sort((a, b) => + String(b.createdAt ?? '').localeCompare(String(a.createdAt ?? '')), + ); + for (const iv of sortedIvs) { interventionById.set(iv.id, iv); + const colNode = colNodeFor.get(iv.collisionId); + if (!colNode || ivNodeForCol.has(colNode)) continue; const ivNode = upsertNode(b, 'intervention', iv.id, { label: iv.suggestedAction?.kind === 'open_sync_pr' @@ -225,9 +281,8 @@ export async function materializePodGraph(podId: string): Promise learned_from edges + owns. @@ -237,16 +292,41 @@ export async function materializePodGraph(podId: string): Promise { + b.nodes.delete(id); + for (const [eid, e] of [...b.edges]) + if (e.source === id || e.target === id) b.edges.delete(eid); + }; + for (const [id, n] of [...b.nodes]) { + if (n.kind === 'engineer' && ENGINEER_NOISE.test(n.label)) dropNode(id); + } + // Collisions with no remaining engineer = test/orphan -> drop. + for (const [id, n] of [...b.nodes]) { + if (n.kind !== 'collision') continue; + if (![...b.edges.values()].some((e) => e.kind === 'collides' && e.target === id)) dropNode(id); + } + // Files / interventions left with no edges -> drop. + for (const [id, n] of [...b.nodes]) { + if (n.kind === 'file' || n.kind === 'intervention') { + if (![...b.edges.values()].some((e) => e.source === id || e.target === id)) + b.nodes.delete(id); } } @@ -259,7 +339,13 @@ export async function materializePodGraph(podId: string): Promise o.accepted && o.wasRealCollision).length; const totalOutcomes = outcomeDocs.length; - const riskPaths = collisionDocs.filter((col) => new Set(col.engineers).size >= 2).length; + const riskPaths = new Set( + collisionDocs.map( + (col) => + (col as { memorySignature?: string }).memorySignature ?? + `${normalizeFile(col.file)}#${col.symbol ?? ''}`, + ), + ).size; const metrics: PodGraphMetric[] = [ { label: 'Learned owners', diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3ee58fb..a632d13 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -268,10 +268,6 @@ export default function App() { Privacy-limited -