feat(intelligence): add Knowledge Graph Centrality#2931
Conversation
A new read-only "Centrality" tab on the Intelligence page that runs graph algorithms over the memory knowledge graph to surface its structural backbone: the load-bearing HUB entities and — crucially — the BRIDGE/connector entities that link otherwise-separate clusters, which a raw frequency/evidence sort cannot reveal. - Pure deterministic engine (lib/memory/graphCentrality.ts): directed PageRank (damping 0.85, Jacobi power iteration with dangling-mass redistribution so Σrank == 1), in/out/total + evidence-weighted degree, and weakly-connected component counting via union-find. No clock, no RNG; node ordering is pinned by a total string order so the same graph always yields byte-identical ranks. Known-value fixtures (2-node, cycle, dangling sink, weighted, 4-node mixed, self-loops) are asserted to 6 decimals. - Zero new core surface: reuses the already-shipped memoryGraphQuery and memoryListNamespaces RPC wrappers. Read-only — the result is recomputed from the live graph, never persisted. - Container/presentational split; namespace selector; metric tiles + a top-25 ranked table with a "connector" badge on entities whose influence outranks their link count. i18n across all 13 locales. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughWalkthroughAdds a deterministic PageRank centrality engine, RPC facade, React tab + panel UI, comprehensive tests, and i18n translations to show ranked entities, metrics, and detected bridge/connector nodes. ChangesKnowledge Graph Centrality Feature
Sequence DiagramsequenceDiagram
participant User
participant IntelligencePage
participant GraphCentralityTab
participant graphCentralityApi
participant TauriRPC
participant CentralityEngine
participant GraphCentralityPanel
User->>IntelligencePage: Click Centrality Tab
IntelligencePage->>GraphCentralityTab: Mount (activeTab='centrality')
GraphCentralityTab->>graphCentralityApi: loadCentrality(undefined)
graphCentralityApi->>TauriRPC: memoryGraphQuery(undefined)
TauriRPC-->>graphCentralityApi: GraphRelation[]
graphCentralityApi->>CentralityEngine: computeGraphCentrality(relations)
CentralityEngine-->>graphCentralityApi: CentralityResult
graphCentralityApi-->>GraphCentralityTab: CentralityResult
GraphCentralityTab->>GraphCentralityPanel: result, loading=false
GraphCentralityPanel-->>User: Ranked table + metrics
User->>GraphCentralityTab: Select namespace
GraphCentralityTab->>graphCentralityApi: loadCentrality(namespace)
graphCentralityApi->>TauriRPC: memoryGraphQuery(namespace)
TauriRPC-->>graphCentralityApi: GraphRelation[]
graphCentralityApi->>CentralityEngine: computeGraphCentrality(relations)
CentralityEngine-->>graphCentralityApi: CentralityResult
graphCentralityApi-->>GraphCentralityTab: CentralityResult
GraphCentralityTab->>GraphCentralityPanel: updated result
GraphCentralityPanel-->>User: Updated ranked table
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
graycyrus
left a comment
There was a problem hiding this comment.
@aashir-athar hey! the code looks good to me, but CI is still pending on this one. once those checks are green, i'll come back and approve this. let me know if you need any help!
Quick notes from the read-through while i'm here:
The engine is solid. Deterministic node ordering, correct dangling-mass redistribution in the PageRank loop, safe self-loop handling in union-find, proper evidence weight sanitisation. The nested-map edge collapse avoids any separator-collision footgun that a string-concatenated key would have introduced. The 32 tests cover the right surface — known-value fixtures, the dangling-mass canary, the bridge/connector heuristic, and the container error path.
One nit: const maxRank = result.nodes[0].pageRank || 1 in GraphCentralityPanel.tsx would silently use 1 if pageRank ever comes back as 0 (unlikely but possible on a fully-isolated single node with only a self-loop). An explicit result.nodes[0].pageRank > 0 ? result.nodes[0].pageRank : 1 makes the intent clear. Not blocking.
Overall this is a well-scoped read-only addition with zero new RPC surface and no changes to shared types or state. Clean once CI is green.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/components/intelligence/GraphCentralityPanel.tsx`:
- Around line 205-209: The degree breakdown is only in the title attribute (in
GraphCentralityPanel) which screen-readers may ignore; update the TD that
currently sets title={t('graphCentrality.degreeTitle').replace('{in}',
String(node.inDegree)).replace('{out}', String(node.outDegree))} to also expose
the same text to assistive tech — either add an aria-label with that same
replaced string or include a visually hidden span (e.g., class "sr-only")
containing the t('graphCentrality.degreeTitle') text with node.inDegree and
node.outDegree substituted; keep the existing title for hover tooltips but
ensure the aria-label/hidden span uses node.inDegree and node.outDegree so
screen-readers get the in/out breakdown.
In `@app/src/components/intelligence/GraphCentralityTab.tsx`:
- Around line 21-30: The load callback can apply stale results when overlapping
calls resolve out-of-order; modify load (which calls loadCentrality and then
setResult/setError/setLoading) to ignore responses from earlier requests by
tracking a per-call token (e.g., incrementing requestId or using an
AbortController stored in a ref) and only calling setResult/setError if the
token matches the latest; ensure the token is updated at start of load and
cleaned up on unmount, and keep setLoading(false) guarded so only the latest
request flips loading off.
In `@app/src/lib/memory/graphCentrality.ts`:
- Around line 265-281: The current bridge detection uses ordinal positions
(pageRankPosition and degreePosition) that break ties by id; change these to
dense ranks based on the metric values so tied nodes share the same rank. For
pageRankPosition, iterate nodes sorted by pageRank desc and assign a rank that
only increments when node.pageRank value decreases (so equal pageRank → same
rank); for degreePosition, sort by totalDegree desc (remove the id tiebreak from
byDegree) and assign dense ranks the same way based on node.totalDegree. Keep
the rest of the logic (gap = degreeRank - pageRankRank, pushing into bridges)
the same and reference the same variables pageRankPosition, degreePosition,
byDegree, minGap and bridges.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4e78d44e-6679-4230-8e43-a1d6b7e7985b
📒 Files selected for processing (23)
app/src/components/intelligence/GraphCentralityPanel.test.tsxapp/src/components/intelligence/GraphCentralityPanel.tsxapp/src/components/intelligence/GraphCentralityTab.test.tsxapp/src/components/intelligence/GraphCentralityTab.tsxapp/src/lib/i18n/chunks/ar-1.tsapp/src/lib/i18n/chunks/bn-1.tsapp/src/lib/i18n/chunks/de-1.tsapp/src/lib/i18n/chunks/en-1.tsapp/src/lib/i18n/chunks/es-1.tsapp/src/lib/i18n/chunks/fr-1.tsapp/src/lib/i18n/chunks/hi-1.tsapp/src/lib/i18n/chunks/id-1.tsapp/src/lib/i18n/chunks/it-1.tsapp/src/lib/i18n/chunks/ko-1.tsapp/src/lib/i18n/chunks/pt-1.tsapp/src/lib/i18n/chunks/ru-1.tsapp/src/lib/i18n/chunks/zh-CN-1.tsapp/src/lib/i18n/en.tsapp/src/lib/memory/graphCentrality.test.tsapp/src/lib/memory/graphCentrality.tsapp/src/pages/Intelligence.tsxapp/src/services/api/graphCentralityApi.test.tsapp/src/services/api/graphCentralityApi.ts
- findBridges now uses tie-aware DENSE ranks on the metric values instead of ordinal array positions with an id tiebreak, so a pure entity rename can no longer add or remove a connector flag (ranking is purely structural). - GraphCentralityTab guards against out-of-order load responses with a monotonic request token, so a slow earlier query can't overwrite a newer one. - The ranked table's degree cell exposes the in/out breakdown to assistive tech via aria-label (title alone is not reliably announced). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Thanks @coderabbitai — all three addressed in c7764bc, verified against the code:
Local suite green: 34 centrality tests, |
|
Tip For best results, initiate chat on the files or code changes.
The 34-test green suite with clean tsc/ESLint/Prettier is a great sign. 🎉 (◕‿◕✿) 🐇 If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! |
graycyrus
left a comment
There was a problem hiding this comment.
@aashir-athar the fix commit looks solid — race condition guard with the request ID ref is the right call, and switching bridge detection to dense ranks removes the name-ordering sensitivity that CodeRabbit flagged.
One leftover minor from my first pass that didn't make it into the fix commit: in GraphCentralityPanel.tsx, const maxRank = result.nodes[0].pageRank || 1 will silently fall back to 1 if pageRank is exactly 0 (technically possible on a single isolated node). Prefer result.nodes[0].pageRank > 0 ? result.nodes[0].pageRank : 1. Not blocking, but clean it up when convenient.
Code looks good otherwise. I'll come back and approve once CI is fully green.
|
Heads-up on CI: the I don't have rerun rights on the repo, so I pushed an empty commit to re-trigger the lane. If it flakes again, a maintainer re-run should clear it. |
Summary
Adds a new read-only "Centrality" tab on the Intelligence page that runs graph algorithms over the memory knowledge graph to surface its structural backbone — the load-bearing hub entities, and crucially the bridge/connector entities that link otherwise-separate clusters.
The existing memory surfaces show the graph spatially or sort it by raw evidence count; neither can reveal a low-frequency entity that is nonetheless structurally central because it connects disjoint clusters. PageRank can — that contrast is the point of this lens.
What it computes (pure deterministic engine)
d·D/N) so the rank vector sums to 1 every iteration.Design
lib/memory/graphCentrality.ts): no clock, no RNG. Node order is pinned by a total string ordering and PageRank uses Jacobi iteration into a fresh array, so the same graph always yields byte-identical ranks regardless of relation insertion order. Known-value fixtures (2-node mutual, directed cycle, dangling sink, evidence-weighted, 4-node mixed with a closed-form(1-d)/Nanchor, self-loops) are asserted to 6 decimals; a dangling-mass canary asserts Σrank = 1.memoryGraphQueryandmemoryListNamespacesRPC wrappers. Read-only — the result is recomputed from the live graph on mount, never persisted (no staleness, no new store).Edge cases handled (and tested)
Empty graph, single node, self-loop, dangling sink, disconnected components, duplicate/multi-predicate edge collapse (weights summed), zero/negative/NaN
evidenceCount(clamped to ≥1), case/whitespace-variant entities (kept distinct by design), malformed rows (dropped), entities containing the separator (no collision — nested-map collapse), and the iteration cap (converged: falsefallback, never hangs).Test plan
vitest— 32 tests (engine fixtures + determinism + degree/collapse/sanitization + bridges; api facade; presentational panel; container load/selector/error)tsc --noEmit— cleaneslint— 0 errorsprettier --check— cleangraphCentrality.*keys🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Internationalization
Tests