Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
854c244
feat(intelligence): add Graph Cohesion
aashir-athar May 29, 2026
d296641
chore: re-trigger CI
aashir-athar May 29, 2026
39bbd8e
chore: re-trigger CI (flaky Rust test)
aashir-athar May 29, 2026
f9175e0
fix(intelligence): Graph Cohesion — FP rounding, findBrokers cap, i18…
May 30, 2026
3831b2b
chore: merge upstream/main — resolve conflicts + apply CodeRabbit fixes
May 30, 2026
c503df9
fix(i18n): remove duplicate graphCohesion keys from en.ts
May 30, 2026
d00520b
fix: remove leftover git conflict markers in graphCohesion.ts and Int…
May 30, 2026
b8bce4d
style: prettier-format graphCohesion.ts after conflict resolution
May 30, 2026
0f27a5d
refactor(graphCohesion): async/await in useEffect, stable test assertion
May 31, 2026
1a61986
ci: retrigger after transient Ollama availability flake in inference_…
May 31, 2026
0ce13f1
ci: retrigger (lower-case comment tweak to force fresh check suite)
May 31, 2026
730faa5
ci: retrigger #2 (persistent Rust coverage E2E infra flakes, unrelate…
May 31, 2026
9a4ec23
fix(test): correct escaped-newline assertion in inference_local_servi…
May 31, 2026
2d5becc
fix(test): add ENV_LOCK mutex to prevent parallel-test env-var races …
May 31, 2026
de6c4f0
Merge remote-tracking branch 'upstream/main' into fix/pr-2978-cohesion
May 31, 2026
4ed23ff
fix(test): add env_lock() to round22/23 coverage suites (matching #30…
May 31, 2026
1b59021
fix(test): correct toolkit_from_slug assertion in memory_raw_coverage…
May 31, 2026
2391d2b
fix(test): update fake gh paths for fetch_all_pages pagination format
May 31, 2026
40b2349
fix: replace heredoc with printf in fake gh script for proper JSON ou…
Jun 1, 2026
4fe0613
fix(test): restore memoryGraphLayout files missing from upstream merge
Jun 1, 2026
24464f5
Merge remote-tracking branch 'upstream/main' into fix/pr-2978-cohesion
Jun 1, 2026
4884baa
fix(test): update nodeRadius test + resolve upstream Rust test conflict
Jun 1, 2026
9934438
Merge remote-tracking branch 'upstream/main' into fix/pr-2978-cohesion
Jun 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/src/components/intelligence/GraphCohesionTab.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ describe('<GraphCohesionTab />', () => {
it('loads cohesion (all namespaces) on mount and renders the result', async () => {
render(<GraphCohesionTab />);
expect(mockLoadCohesion).toHaveBeenCalledWith(undefined);
await waitFor(() =>
expect(screen.getByText('Brokers — loosest neighbourhoods')).toBeInTheDocument()
);
// Assert on the broker table rather than a locale-specific heading so the
// test does not break on copy-only or i18n changes.
await waitFor(() => expect(screen.getByRole('table')).toBeInTheDocument());
});

it('shows the namespace selector and re-queries on change', async () => {
Expand Down
12 changes: 9 additions & 3 deletions app/src/components/intelligence/GraphCohesionTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,15 @@ const GraphCohesionTab = () => {
useEffect(() => {
// Namespaces are optional UI sugar; a failure to list them must not block
// the cohesion view, so swallow that error specifically.
loadNamespaces()
.then(setNamespaces)
.catch(() => setNamespaces([]));
const loadNamespaceOptions = async (): Promise<void> => {
try {
setNamespaces(await loadNamespaces());
} catch {
setNamespaces([]);
}
};

void loadNamespaceOptions();
void load('');
}, [load]);

Expand Down
5 changes: 3 additions & 2 deletions app/src/lib/memory/graphCohesion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,9 @@ describe('computeGraphCohesion — diamond (two triangles sharing an edge)', ()
});

it('average clustering = mean over the four clusterable nodes', () => {
// (1 + 1 + 2/3 + 2/3) / 4 = 5/6
expect(r.averageClustering).toBeCloseTo(5 / 6, 12);
// (1 + 1 + 2/3 + 2/3) / 4 = 5/6 ≈ 0.833333
// Implementation rounds to 6 decimal places, so exact toBe is correct here.
expect(r.averageClustering).toBe(0.833333);
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

it('transitivity = 3·triangles / connected-triples = 6/8', () => {
Expand Down
13 changes: 9 additions & 4 deletions app/src/lib/memory/graphCohesion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Graph Cohesion — pure clustering-coefficient & triangle engine.
*
* The memory knowledge graph is a set of (subject)-[predicate]->(object)
* triples. This lens forgets edge DIRECTION and asks a different question from
* triples. This lens forgets edge direction and asks a different question from
* the centrality lens: not "which entities are important" but "how tightly knit
* is the neighbourhood AROUND each entity". Two structural signals fall out:
*
Expand Down Expand Up @@ -147,13 +147,18 @@ export function computeGraphCohesion(relations: GraphRelation[]): CohesionResult
}
}

// Round to 6 decimal places before returning. Floating-point results may
// differ at the ULP level between x86-64 and ARM (different FPU rounding),
// so rounding to a display-relevant precision makes the displayed value
// stable across platforms and devices.
const round6 = (x: number) => Math.round(x * 1e6) / 1e6;
Comment thread
staimoorulhassan marked this conversation as resolved.
return {
nodes,
nodeCount: adjacency.size,
edgeCount: edgeDegreeSum / 2,
triangleCount: closedTripleSum / 3,
averageClustering: clusterableCount === 0 ? 0 : clusteringSum / clusterableCount,
transitivity: connectedTriples === 0 ? 0 : closedTripleSum / connectedTriples,
averageClustering: clusterableCount === 0 ? 0 : round6(clusteringSum / clusterableCount),
transitivity: connectedTriples === 0 ? 0 : round6(closedTripleSum / connectedTriples),
};
}

Expand All @@ -164,7 +169,7 @@ export function computeGraphCohesion(relations: GraphRelation[]): CohesionResult
* disconnected. Sorted clustering ASC, then degree DESC (a bigger gap brokered
* matters more), then id ASC. Pure; derived entirely from the result.
*/
export function findBrokers(result: CohesionResult, limit = 25): CohesionNode[] {
export function findBrokers(result: CohesionResult, limit = 100): CohesionNode[] {
return result.nodes
.filter(node => node.degree >= 2)
.sort((a, b) => {
Expand Down
Loading