Skip to content

feat(intelligence): add Knowledge Graph Centrality#2931

Merged
graycyrus merged 3 commits into
tinyhumansai:mainfrom
aashir-athar:feat/knowledge-graph-centrality
May 29, 2026
Merged

feat(intelligence): add Knowledge Graph Centrality#2931
graycyrus merged 3 commits into
tinyhumansai:mainfrom
aashir-athar:feat/knowledge-graph-centrality

Conversation

@aashir-athar
Copy link
Copy Markdown
Contributor

@aashir-athar aashir-athar commented May 29, 2026

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)

  • PageRank — directed, damping 0.85, Jacobi power iteration. Dangling/sink nodes' mass is redistributed uniformly (d·D/N) so the rank vector sums to 1 every iteration.
  • Degree centrality — in / out / total, plus an evidence-weighted variant.
  • Connected components — weakly-connected island count via union-find (self-loops add no connectivity).
  • Bridge detection — flags entities whose PageRank rank far outranks their degree rank (the connector signal).

Design

  • Pure & deterministic (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)/N anchor, self-loops) are asserted to 6 decimals; a dangling-mass canary asserts Σrank = 1.
  • Zero new core surface: reuses the already-shipped memoryGraphQuery and memoryListNamespaces RPC wrappers. Read-only — the result is recomputed from the live graph on mount, never persisted (no staleness, no new store).
  • Container/presentational split; namespace selector; 3 metric tiles (Entities / Connections / Clusters) + a top-25 ranked table with an inline influence bar and a "connector" badge. i18n across all 13 locales.

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: false fallback, never hangs).

Test plan

  • vitest — 32 tests (engine fixtures + determinism + degree/collapse/sanitization + bridges; api facade; presentational panel; container load/selector/error)
  • tsc --noEmit — clean
  • eslint — 0 errors
  • prettier --check — clean
  • i18n coverage gate — EXIT 0, no unused graphCentrality.* keys

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a Centrality tab in Intelligence showing entity influence rankings, metric tiles (entities, connections, clusters), connector/bridge badges, ranked hubs, and namespace filtering with retry on error.
  • Internationalization

    • Added translations for the Centrality UI across 13+ languages.
  • Tests

    • Added extensive unit and UI tests for centrality computation, bridge detection, API integration, and the Centrality UI components.

Review Change Stack

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>
@aashir-athar aashir-athar requested a review from a team May 29, 2026 10:27
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f6e555d9-7ba4-44b5-8dab-9cb925fd4f09

📥 Commits

Reviewing files that changed from the base of the PR and between c00e6dd and c7764bc.

📒 Files selected for processing (5)
  • app/src/components/intelligence/GraphCentralityPanel.test.tsx
  • app/src/components/intelligence/GraphCentralityPanel.tsx
  • app/src/components/intelligence/GraphCentralityTab.tsx
  • app/src/lib/memory/graphCentrality.test.ts
  • app/src/lib/memory/graphCentrality.ts
✅ Files skipped from review due to trivial changes (1)
  • app/src/lib/memory/graphCentrality.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • app/src/components/intelligence/GraphCentralityTab.tsx
  • app/src/components/intelligence/GraphCentralityPanel.test.tsx
  • app/src/components/intelligence/GraphCentralityPanel.tsx
  • app/src/lib/memory/graphCentrality.ts

📝 Walkthrough

Walkthrough

Adds 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.

Changes

Knowledge Graph Centrality Feature

Layer / File(s) Summary
Core centrality computation engine
app/src/lib/memory/graphCentrality.ts, app/src/lib/memory/graphCentrality.test.ts
Deterministic PageRank-like engine with edge collapsing, in/out degree metrics, union-find components, dangling-node redistribution, tolerance/max-iteration control, dense ranking, and bridge detection; fully tested for distributions, determinism, sanitization, and convergence behavior.
API facade and Tauri RPC integration
app/src/services/api/graphCentralityApi.ts, app/src/services/api/graphCentralityApi.test.ts
RPC wrapper that calls memoryGraphQuery/memoryListNamespaces, converts triples to centrality via computeGraphCentrality, and exposes loadCentrality(namespace?) and loadNamespaces() with tests for namespace passthrough, empty handling, and error propagation.
Centrality display panel component
app/src/components/intelligence/GraphCentralityPanel.tsx, app/src/components/intelligence/GraphCentralityPanel.test.tsx
Presentational component with memoized bridge detection and largest-cluster calc; renders intro, loading skeleton, error alert with Retry, empty hints, metric tiles, cluster caption (approx badge when not converged), and a ranked table with bridge badges, influence bars, and degree tooltips. Tests cover all state branches and visual assertions.
Tab container with state management
app/src/components/intelligence/GraphCentralityTab.tsx, app/src/components/intelligence/GraphCentralityTab.test.tsx
Container that loads centrality on mount and when namespace changes, uses request tokens to ignore stale responses, optionally renders namespace selector, and delegates rendering to GraphCentralityPanel with retry behavior; tested for mount load, namespace re-query, and error rendering.
Intelligence page integration
app/src/pages/Intelligence.tsx
Registers and renders the new centrality tab by importing GraphCentralityTab, extending the IntelligenceTab union, and adding the tab entry to the tabs array.
Internationalization
app/src/lib/i18n/chunks/{ar,bn,de,en,es,fr,hi,id,it,ko,pt,ru,zh-CN}-1.ts, app/src/lib/i18n/en.ts
Added translation keys for the centrality tab label and the graph centrality UI (title, intro, loading/error/empty text, namespace label, metric labels, cluster caption, approximate badge, ranked table headers, connector/degree labels) across multiple locale chunks and main English file.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • graycyrus
  • M3gA-Mind

🐰 I hop through graphs to find each thread,
PageRank whispers paths where thoughts are led,
Bridges flagged and rankings softly gleam,
In many tongues we share this knowledge dream.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(intelligence): add Knowledge Graph Centrality' clearly summarizes the main change: adding a Knowledge Graph Centrality feature to the Intelligence page.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added feature Net-new user-facing capability or product behavior. working A PR that is being worked on by the team. labels May 29, 2026
Copy link
Copy Markdown
Contributor

@graycyrus graycyrus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 04286cb and c00e6dd.

📒 Files selected for processing (23)
  • app/src/components/intelligence/GraphCentralityPanel.test.tsx
  • app/src/components/intelligence/GraphCentralityPanel.tsx
  • app/src/components/intelligence/GraphCentralityTab.test.tsx
  • app/src/components/intelligence/GraphCentralityTab.tsx
  • app/src/lib/i18n/chunks/ar-1.ts
  • app/src/lib/i18n/chunks/bn-1.ts
  • app/src/lib/i18n/chunks/de-1.ts
  • app/src/lib/i18n/chunks/en-1.ts
  • app/src/lib/i18n/chunks/es-1.ts
  • app/src/lib/i18n/chunks/fr-1.ts
  • app/src/lib/i18n/chunks/hi-1.ts
  • app/src/lib/i18n/chunks/id-1.ts
  • app/src/lib/i18n/chunks/it-1.ts
  • app/src/lib/i18n/chunks/ko-1.ts
  • app/src/lib/i18n/chunks/pt-1.ts
  • app/src/lib/i18n/chunks/ru-1.ts
  • app/src/lib/i18n/chunks/zh-CN-1.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/memory/graphCentrality.test.ts
  • app/src/lib/memory/graphCentrality.ts
  • app/src/pages/Intelligence.tsx
  • app/src/services/api/graphCentralityApi.test.ts
  • app/src/services/api/graphCentralityApi.ts

Comment thread app/src/components/intelligence/GraphCentralityPanel.tsx
Comment thread app/src/components/intelligence/GraphCentralityTab.tsx
Comment thread app/src/lib/memory/graphCentrality.ts Outdated
- 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>
@aashir-athar
Copy link
Copy Markdown
Contributor Author

Thanks @coderabbitai — all three addressed in c7764bc, verified against the code:

  1. Tie-aware bridge ranks (graphCentrality.ts)findBridges now computes dense ranks on the metric values (a new denseRanksDesc helper) for both PageRank and total degree, with the id tiebreak removed. Tied nodes share a rank, so a pure rename can no longer add/remove a connector flag — ranking is purely structural. Added a name-independence test (relabel every entity → identical gaps) and a tie test (mutual A↔B → no spurious bridge).

  2. Out-of-order responses (GraphCentralityTab.tsx)load now stamps a monotonic latestRequestId ref and ignores its result/error/loading updates if a newer load has started, so the mount-time load('') can't overwrite a later namespace-change result.

  3. Degree breakdown a11y (GraphCentralityPanel.tsx) — the degree cell now carries an aria-label with the same {in} in · {out} out text (kept title for hover), so screen readers get the breakdown.

Local suite green: 34 centrality tests, tsc --noEmit, ESLint (0 errors), Prettier.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 29, 2026

Tip

For best results, initiate chat on the files or code changes.

@aashir-athar: All three fixes look solid — thank you for the detailed follow-up!

  1. Dense-rank bridges — using denseRanksDesc on metric values and removing the id tiebreak is exactly the right approach; adding the name-independence and mutual-link tie tests gives strong structural guarantees.
  2. Race-condition guard — the monotonic latestRequestId pattern is a clean way to make concurrent loads safe without adding complex cancellation logic.
  3. aria-label on degree cell — keeping title for hover while adding aria-label for screen readers is the correct dual-attribute pattern.

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!

Copy link
Copy Markdown
Contributor

@graycyrus graycyrus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

@aashir-athar
Copy link
Copy Markdown
Contributor Author

Heads-up on CI: the E2E (Linux / Appium Chromium) lane went red on the previous commit, but the failure was in the Build E2E app step — a cold-cache Rust/Tauri compile that hit a GitHub Actions cache-reservation rate limit ("You've hit a rate limit") and didn't finish — not a test assertion. This PR's changes are TypeScript-only (dense-rank bridge math, a request-token guard, one aria-label), the Rust build is identical to the prior green commit, all other lanes are green, and this same E2E lane is success on main. So it's an infra flake, not a regression.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Net-new user-facing capability or product behavior. working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants