From d9e340b05d032df0cf4da966cd0b7aa5d6d1da23 Mon Sep 17 00:00:00 2001 From: abhigyanpatwari Date: Sun, 10 May 2026 04:14:28 +0530 Subject: [PATCH] feat(communities): seed Leiden RNG for deterministic community detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The vendored Leiden algorithm defaults to Math.random for tie-breaking and randomized walks, which produces non-deterministic community assignments and modularity values across runs on the same graph. Pass a seeded mulberry32 RNG (LEIDEN_SEED=0xC0DE) so: - The same graph always produces the same partition - Modularity values are reproducible - Equivalence tests for incremental indexing can compare community assignments byte-for-byte This is foundational for the upcoming incremental-indexing feature (see docs/superpowers/specs/2026-05-10-incremental-indexing-design.md) where the correctness contract is incremental output ≡ full rebuild output. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/core/ingestion/community-processor.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/gitnexus/src/core/ingestion/community-processor.ts b/gitnexus/src/core/ingestion/community-processor.ts index 9913e4a3f..ac8f068fa 100644 --- a/gitnexus/src/core/ingestion/community-processor.ts +++ b/gitnexus/src/core/ingestion/community-processor.ts @@ -41,6 +41,24 @@ interface LeidenDetailedResult { modularity: number; } +/** + * Deterministic PRNG (mulberry32) seed for the vendored Leiden algorithm. + * Vendored Leiden defaults `rng: Math.random`, which makes community + * assignment non-deterministic across runs. Passing a seeded RNG gives us + * reproducible community/modularity output, which is required for the + * incremental-indexing equivalence test (incremental ≡ full rebuild). + */ +const LEIDEN_SEED = 0xc0de; +function createSeededRng(seed: number): () => number { + let s = seed >>> 0; + return () => { + s = (s + 0x6d2b79f5) >>> 0; + let t = Math.imul(s ^ (s >>> 15), 1 | s); + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +} + // ============================================================================ // TYPES // ============================================================================ @@ -150,6 +168,7 @@ export const processCommunities = async ( leiden.detailed(graph, { resolution: isLarge ? 2.0 : 1.0, maxIterations: isLarge ? 3 : 0, + rng: createSeededRng(LEIDEN_SEED), }), ), new Promise((_, reject) =>