feat(intelligence): add Graph Cohesion#2978
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a deterministic graph-cohesion engine, a read-only API wrapper, a stateful Cohesion tab and presentational panel with loading/error/empty/success states, tests for computation and UI flows, page wiring to expose the Cohesion tab, and i18n keys across locales. ChangesGraph Cohesion Feature
Sequence DiagramsequenceDiagram
participant User
participant IntelligencePage
participant GraphCohesionTab
participant graphCohesionApi
participant memoryGraphQuery
participant computeGraphCohesion
User->>IntelligencePage: open Cohesion tab
IntelligencePage->>GraphCohesionTab: mount
GraphCohesionTab->>graphCohesionApi: loadCohesion(namespace)
graphCohesionApi->>memoryGraphQuery: memoryGraphQuery(namespace)
memoryGraphQuery-->>graphCohesionApi: GraphRelation[]
graphCohesionApi->>computeGraphCohesion: computeGraphCohesion(relations)
computeGraphCohesion-->>graphCohesionApi: CohesionResult
graphCohesionApi-->>GraphCohesionTab: CohesionResult
GraphCohesionTab-->>User: render metrics & brokers
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 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. 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 |
3182427 to
42888ae
Compare
Rust Core Coverage failed in its post-step with "No space left on device" during cache save — a runner-disk infra flake unrelated to this TS-only PR (Rust is byte-identical to main; the same flake hit tinyhumansai#2978 previously). Co-Authored-By: Claude Opus 4.7 <[email protected]>
42888ae to
71bc99a
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/lib/memory/graphCohesion.test.ts`:
- Around line 206-218: The inline comment in the test "sorts nodes by clustering
DESC, then degree DESC, then id ASC" is incorrect: update the note that
currently says "B,C also 1 here" to reflect that B and C have localClustering
2/3 (degree 3) and only A and D have clustering 1; edit the comment near the
computeGraphCohesion call or the rel(...) list so it accurately documents the
diamond shape and that the ones array (derived from r.nodes.filter(...).map(n =>
n.id)) should be ['A','D'].
🪄 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: 074eeae0-b6d9-49cc-8b4e-49110efce73e
📒 Files selected for processing (23)
app/src/components/intelligence/GraphCohesionPanel.test.tsxapp/src/components/intelligence/GraphCohesionPanel.tsxapp/src/components/intelligence/GraphCohesionTab.test.tsxapp/src/components/intelligence/GraphCohesionTab.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/graphCohesion.test.tsapp/src/lib/memory/graphCohesion.tsapp/src/pages/Intelligence.tsxapp/src/services/api/graphCohesionApi.test.tsapp/src/services/api/graphCohesionApi.ts
✅ Files skipped from review due to trivial changes (4)
- app/src/lib/i18n/chunks/it-1.ts
- app/src/lib/i18n/en.ts
- app/src/lib/i18n/chunks/fr-1.ts
- app/src/lib/i18n/chunks/es-1.ts
🚧 Files skipped from review as they are similar to previous changes (14)
- app/src/components/intelligence/GraphCohesionTab.test.tsx
- app/src/pages/Intelligence.tsx
- app/src/lib/i18n/chunks/bn-1.ts
- app/src/services/api/graphCohesionApi.ts
- app/src/lib/i18n/chunks/id-1.ts
- app/src/lib/i18n/chunks/ar-1.ts
- app/src/services/api/graphCohesionApi.test.ts
- app/src/lib/i18n/chunks/en-1.ts
- app/src/lib/i18n/chunks/ru-1.ts
- app/src/components/intelligence/GraphCohesionTab.tsx
- app/src/lib/i18n/chunks/zh-CN-1.ts
- app/src/components/intelligence/GraphCohesionPanel.test.tsx
- app/src/lib/memory/graphCohesion.ts
- app/src/components/intelligence/GraphCohesionPanel.tsx
| it('sorts nodes by clustering DESC, then degree DESC, then id ASC', () => { | ||
| const r = computeGraphCohesion([ | ||
| rel('A', 'B'), | ||
| rel('A', 'C'), | ||
| rel('B', 'C'), // triangle A-B-C (clustering 1 for A; B,C also 1 here) | ||
| rel('B', 'D'), | ||
| rel('C', 'D'), // diamond | ||
| ]); | ||
| // top entries are the clustering-1 nodes; A before D by id at equal degree. | ||
| expect(r.nodes[0].localClustering).toBe(1); | ||
| const ones = r.nodes.filter(n => n.localClustering === 1).map(n => n.id); | ||
| expect(ones).toEqual(['A', 'D']); | ||
| }); |
There was a problem hiding this comment.
Inline comment contradicts the assertion below it.
The comment claims B,C also 1 here, but in this diamond B and C have degree 3 and cluster at 2/3 — only A and D are at 1, which is exactly what the ones assertion verifies. The misleading note could prompt a future reader to "fix" a correct test.
✏️ Suggested comment correction
- rel('B', 'C'), // triangle A-B-C (clustering 1 for A; B,C also 1 here)
+ rel('B', 'C'), // triangle A-B-C; A & D cluster at 1, B & C at 2/3 (diamond spine)
rel('B', 'D'),
rel('C', 'D'), // diamond📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| it('sorts nodes by clustering DESC, then degree DESC, then id ASC', () => { | |
| const r = computeGraphCohesion([ | |
| rel('A', 'B'), | |
| rel('A', 'C'), | |
| rel('B', 'C'), // triangle A-B-C (clustering 1 for A; B,C also 1 here) | |
| rel('B', 'D'), | |
| rel('C', 'D'), // diamond | |
| ]); | |
| // top entries are the clustering-1 nodes; A before D by id at equal degree. | |
| expect(r.nodes[0].localClustering).toBe(1); | |
| const ones = r.nodes.filter(n => n.localClustering === 1).map(n => n.id); | |
| expect(ones).toEqual(['A', 'D']); | |
| }); | |
| it('sorts nodes by clustering DESC, then degree DESC, then id ASC', () => { | |
| const r = computeGraphCohesion([ | |
| rel('A', 'B'), | |
| rel('A', 'C'), | |
| rel('B', 'C'), // triangle A-B-C; A & D cluster at 1, B & C at 2/3 (diamond spine) | |
| rel('B', 'D'), | |
| rel('C', 'D'), // diamond | |
| ]); | |
| // top entries are the clustering-1 nodes; A before D by id at equal degree. | |
| expect(r.nodes[0].localClustering).toBe(1); | |
| const ones = r.nodes.filter(n => n.localClustering === 1).map(n => n.id); | |
| expect(ones).toEqual(['A', 'D']); | |
| }); |
🤖 Prompt for 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.
In `@app/src/lib/memory/graphCohesion.test.ts` around lines 206 - 218, The inline
comment in the test "sorts nodes by clustering DESC, then degree DESC, then id
ASC" is incorrect: update the note that currently says "B,C also 1 here" to
reflect that B and C have localClustering 2/3 (degree 3) and only A and D have
clustering 1; edit the comment near the computeGraphCohesion call or the
rel(...) list so it accurately documents the diamond shape and that the ones
array (derived from r.nodes.filter(...).map(n => n.id)) should be ['A','D'].
Rust Core Coverage failed in its post-step with "No space left on device" during cache save — a runner-disk infra flake unrelated to this TS-only PR (Rust is byte-identical to main; the same flake hit tinyhumansai#2978 previously). Co-Authored-By: Claude Opus 4.7 <[email protected]>
…rmation) A new read-only "Specialisation" tab. The vocabulary lens (tinyhumansai#17 Predicate Diversity) tells you how varied the global predicate set is. The thickness lens (tinyhumansai#18 Predicate Bundles) tells you which entity pairs share many predicates. The frequency lens (tinyhumansai#7 Relationship Types) tells you which predicates dominate overall. None of them answers the question this lens does: how much does knowing the SUBJECT predict which predicate it will use? And which entities speak in a specialised vocabulary versus a generalist one? Engine (pure, deterministic — no React/RPC/clock/RNG): - Global: I(S; P) in bits with canonical-order summation; H(S) and H(P); normalisedMI = I / min(H(S), H(P)). - Per-subject: specialisation = 1 - H(P|S=s)/log2(D_s) in [0,1] (1 when D_s <= 1 — single-predicate subject is maximally specialised by convention), plus dominantPredicate (tie-broken by predicate ASC) and dominantPredicateShare. - All p·log2(p) and joint·log2 ratio sums walk pairs in canonical (sortedSubjects, sortedPredicates) order so the result is byte-identical regardless of relation insertion order (lesson from tinyhumansai#2978 Cohesion's float-order bug). Selected from the prior loop-19 design workflow's runner-up (8.35/10 — a genuinely new lens not covered by any of the 19 shipped features). Adds ZERO new core surface: composes the already-shipped memoryGraphQuery / memoryListNamespaces wrappers. Container/presentational split with a monotonic request-token race guard for load-on-mount; i18n across all 13 locales. Co-Authored-By: Claude Opus 4.7 <[email protected]>
Rust Core Coverage failed in its post-step with "No space left on device" during cache save — a runner-disk infra flake unrelated to this TS-only PR (Rust is byte-identical to main; the same flake hit tinyhumansai#2978 previously). Co-Authored-By: Claude Opus 4.7 <[email protected]>
…rmation) A new read-only "Specialisation" tab. The vocabulary lens (tinyhumansai#17 Predicate Diversity) tells you how varied the global predicate set is. The thickness lens (tinyhumansai#18 Predicate Bundles) tells you which entity pairs share many predicates. The frequency lens (tinyhumansai#7 Relationship Types) tells you which predicates dominate overall. None of them answers the question this lens does: how much does knowing the SUBJECT predict which predicate it will use? And which entities speak in a specialised vocabulary versus a generalist one? Engine (pure, deterministic — no React/RPC/clock/RNG): - Global: I(S; P) in bits with canonical-order summation; H(S) and H(P); normalisedMI = I / min(H(S), H(P)). - Per-subject: specialisation = 1 - H(P|S=s)/log2(D_s) in [0,1] (1 when D_s <= 1 — single-predicate subject is maximally specialised by convention), plus dominantPredicate (tie-broken by predicate ASC) and dominantPredicateShare. - All p·log2(p) and joint·log2 ratio sums walk pairs in canonical (sortedSubjects, sortedPredicates) order so the result is byte-identical regardless of relation insertion order (lesson from tinyhumansai#2978 Cohesion's float-order bug). Selected from the prior loop-19 design workflow's runner-up (8.35/10 — a genuinely new lens not covered by any of the 19 shipped features). Adds ZERO new core surface: composes the already-shipped memoryGraphQuery / memoryListNamespaces wrappers. Container/presentational split with a monotonic request-token race guard for load-on-mount; i18n across all 13 locales. Co-Authored-By: Claude Opus 4.7 <[email protected]>
71bc99a to
39bbd8e
Compare
staimoorulhassan
left a comment
There was a problem hiding this comment.
CodeRabbit-style Review — PR #2978
Walkthrough
Adds a read-only "Cohesion" tab to the Intelligence view — clustering-coefficient analysis of the memory knowledge graph. Pure frontend engine (graphCohesion.ts), no new RPC surface. 33 tests covering engine, API facade, and UI states.
Findings
🟠 MAJOR — Cross-platform floating-point non-associativity
The PR notes averageClustering is "summed in canonical (sorted) order so it is byte-identical across input permutations." This holds within a single CPU architecture, but not across architectures — x86-64 and ARM (Apple Silicon, iOS) may produce different FP results for the same sum due to differing FPU rounding modes and instruction selection. If the metric is displayed in the UI (it is — the caption shows averageClustering), users on different devices will see silently different values.
Suggested fix: Round to a fixed number of decimal places before display or before storing the value in the return object:
averageClustering: Math.round(sum / count * 1e6) / 1e6,Alternatively, store the intermediate integer numerator/denominator as a rational and divide only at display time.
🟡 MINOR — findBrokers() has no default result cap
findBrokers(result: CohesionResult, limit?: number): BrokerEntry[]If limit is omitted, the full ranked entity list is returned. For a memory graph with thousands of nodes this materializes a large array in the browser main thread before the UI slices it. Add a sensible default cap:
findBrokers(result: CohesionResult, limit = 100): BrokerEntry[]🟡 MINOR — Verify monotonic request token uses strict equality
The container pattern guards against stale responses with a monotonic token. Ensure the comparison uses === (not >=):
if (token !== currentToken) return; // ✓ strict
// vs.
if (token >= currentToken) return; // ✗ allows a fast burst to overwrite a slow oneA >= comparison can incorrectly drop valid responses when two requests fire in rapid succession.
🟡 MINOR — i18n chunk vs flat-file inconsistency
New keys go into app/src/lib/i18n/en.ts (flat — correct) but the non-English translations only appear in chunks/ files. CLAUDE.md states the chunked layout was retired. Please clarify whether chunks/ is still the live layout, or move translations to the flat per-locale files. (This affects PRs #2975, #2978, #2980, and #2985 identically.)
🔵 NITPICK — GraphCohesionPanel.tsx approaching the 200-line soft cap
At 198 lines it's at the edge. The broker table and the metric tiles are good candidates for extraction into sibling components to keep future growth manageable.
Verdict
One major (cross-platform FP stability), two minors, one shared i18n blocker (see #2975 review). The pure-engine design, determinism argument, and test suite quality are exemplary. Resolve the FP rounding and i18n layout issues before merge.
Rust Core Coverage failed in its post-step with "No space left on device" during cache save — a runner-disk infra flake unrelated to this TS-only PR (Rust is byte-identical to main; the same flake hit tinyhumansai#2978 previously). Co-Authored-By: Claude Opus 4.7 <[email protected]>
39bbd8e to
6610563
Compare
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 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/lib/i18n/de.ts`:
- Around line 4249-4270: The new graphCohesion.* and memory.tab.cohesion entries
currently contain English text; replace each value with a proper German
translation for the keys 'graphCohesion.brokerBadge',
'graphCohesion.brokerTitle', 'graphCohesion.colCohesion',
'graphCohesion.colEntity', 'graphCohesion.colLinks', 'graphCohesion.colRank',
'graphCohesion.empty', 'graphCohesion.emptyHint', 'graphCohesion.errorPrefix',
'graphCohesion.intro', 'graphCohesion.loading',
'graphCohesion.metricConnections', 'graphCohesion.metricEntities',
'graphCohesion.metricTriangles', 'graphCohesion.namespaceAll',
'graphCohesion.namespaceLabel', 'graphCohesion.noBrokers',
'graphCohesion.rankedHeading', 'graphCohesion.retry',
'graphCohesion.summaryCaption', 'graphCohesion.title', and 'memory.tab.cohesion'
in app/src/lib/i18n/de.ts; update each string to a correct, natural German
translation (preserve placeholders like {avg} and {transitivity} and
punctuation) so the German locale mirrors the new English keys with accurate
localized text.
In `@app/src/lib/i18n/fr.ts`:
- Around line 4232-4253: The French locale currently contains English text for
the new graphCohesion keys; update the values for all graphCohesion.* keys
(e.g., 'graphCohesion.brokerBadge', 'graphCohesion.brokerTitle',
'graphCohesion.colCohesion', 'graphCohesion.colEntity',
'graphCohesion.colLinks', 'graphCohesion.colRank', 'graphCohesion.empty',
'graphCohesion.emptyHint', 'graphCohesion.errorPrefix', 'graphCohesion.intro',
'graphCohesion.loading', 'graphCohesion.metricConnections',
'graphCohesion.metricEntities', 'graphCohesion.metricTriangles',
'graphCohesion.namespaceAll', 'graphCohesion.namespaceLabel',
'graphCohesion.noBrokers', 'graphCohesion.rankedHeading', 'graphCohesion.retry',
'graphCohesion.summaryCaption' (keep the {avg} placeholder),
'graphCohesion.title', and 'memory.tab.cohesion') with correct French
translations matching the meanings in app/src/lib/i18n/en.ts, ensuring
placeholders and punctuation are preserved.
In `@app/src/lib/i18n/hi.ts`:
- Around line 4145-4166: Several graphCohesion i18n entries were added in
English; replace each English value with proper Hindi translations for the keys
such as 'graphCohesion.brokerBadge', 'graphCohesion.brokerTitle',
'graphCohesion.colCohesion', 'graphCohesion.colEntity',
'graphCohesion.colLinks', 'graphCohesion.colRank', 'graphCohesion.empty',
'graphCohesion.emptyHint', 'graphCohesion.errorPrefix', 'graphCohesion.intro',
'graphCohesion.loading', 'graphCohesion.metricConnections',
'graphCohesion.metricEntities', 'graphCohesion.metricTriangles',
'graphCohesion.namespaceAll', 'graphCohesion.namespaceLabel',
'graphCohesion.noBrokers', 'graphCohesion.rankedHeading', 'graphCohesion.retry',
'graphCohesion.summaryCaption', 'graphCohesion.title', and 'memory.tab.cohesion'
with accurate, idiomatic Hindi strings (not English), keeping placeholders like
{avg} and {transitivity} intact where present. Ensure translations follow
existing style in other Hindi locale entries and preserve punctuation and
special characters.
In `@app/src/lib/i18n/id.ts`:
- Around line 4155-4176: Translate all new graphCohesion.* keys and
memory.tab.cohesion in this Indonesian locale file from English into proper
Indonesian equivalents; update the values for 'graphCohesion.brokerBadge',
'graphCohesion.brokerTitle', 'graphCohesion.colCohesion',
'graphCohesion.colEntity', 'graphCohesion.colLinks', 'graphCohesion.colRank',
'graphCohesion.empty', 'graphCohesion.emptyHint', 'graphCohesion.errorPrefix',
'graphCohesion.intro', 'graphCohesion.loading',
'graphCohesion.metricConnections', 'graphCohesion.metricEntities',
'graphCohesion.metricTriangles', 'graphCohesion.namespaceAll',
'graphCohesion.namespaceLabel', 'graphCohesion.noBrokers',
'graphCohesion.rankedHeading', 'graphCohesion.retry',
'graphCohesion.summaryCaption', 'graphCohesion.title', and 'memory.tab.cohesion'
with accurate Indonesian translations (not English) following the style of other
locale entries so the Cohesion tab is localized for id users.
In `@app/src/lib/i18n/pl.ts`:
- Around line 4211-4232: The Polish locale contains English strings for the
cohesion UI keys; replace the English values for all graphCohesion keys (e.g.
'graphCohesion.brokerBadge', 'graphCohesion.brokerTitle',
'graphCohesion.colCohesion', 'graphCohesion.colEntity',
'graphCohesion.colLinks', 'graphCohesion.colRank', 'graphCohesion.empty',
'graphCohesion.emptyHint', 'graphCohesion.errorPrefix', 'graphCohesion.intro',
'graphCohesion.loading', 'graphCohesion.metricConnections',
'graphCohesion.metricEntities', 'graphCohesion.metricTriangles',
'graphCohesion.namespaceAll', 'graphCohesion.namespaceLabel',
'graphCohesion.noBrokers', 'graphCohesion.rankedHeading', 'graphCohesion.retry',
'graphCohesion.summaryCaption', 'graphCohesion.title', and
'memory.tab.cohesion') with correct Polish translations (not English),
preserving placeholders like {avg} and {transitivity} and matching
formatting/quoting of the existing locale file.
In `@app/src/lib/i18n/ru.ts`:
- Around line 4179-4200: The new graphCohesion i18n entries in ru.ts are still
in English; replace the English strings for keys like
'graphCohesion.brokerBadge', 'graphCohesion.brokerTitle',
'graphCohesion.colCohesion', 'graphCohesion.colEntity',
'graphCohesion.colLinks', 'graphCohesion.colRank', 'graphCohesion.empty',
'graphCohesion.emptyHint', 'graphCohesion.errorPrefix', 'graphCohesion.intro',
'graphCohesion.loading', 'graphCohesion.metricConnections',
'graphCohesion.metricEntities', 'graphCohesion.metricTriangles',
'graphCohesion.namespaceAll', 'graphCohesion.namespaceLabel',
'graphCohesion.noBrokers', 'graphCohesion.rankedHeading', 'graphCohesion.retry',
'graphCohesion.summaryCaption', 'graphCohesion.title', and 'memory.tab.cohesion'
with proper Russian translations (preserve placeholders like {avg} and
{transitivity} and any punctuation), following the project's i18n guideline to
keep non-English locales in sync with en.ts. Ensure translations are natural
Russian and update only the values in ru.ts.
In `@app/src/lib/i18n/zh-CN.ts`:
- Around line 3941-3962: The zh-CN i18n block for graph cohesion currently
contains English placeholders; replace all 22 keys (e.g.,
graphCohesion.brokerBadge, graphCohesion.brokerTitle, graphCohesion.colCohesion,
graphCohesion.colEntity, graphCohesion.colLinks, graphCohesion.colRank,
graphCohesion.empty, graphCohesion.emptyHint, graphCohesion.errorPrefix,
graphCohesion.intro, graphCohesion.loading, graphCohesion.metricConnections,
graphCohesion.metricEntities, graphCohesion.metricTriangles,
graphCohesion.namespaceAll, graphCohesion.namespaceLabel,
graphCohesion.noBrokers, graphCohesion.rankedHeading, graphCohesion.retry,
graphCohesion.summaryCaption, graphCohesion.title, memory.tab.cohesion) with
accurate Simplified Chinese translations (e.g., brokerBadge => "中介" or "桥接点",
title => "图谱凝聚度") preserving interpolation tokens like {avg} and {transitivity};
ensure phrases keep meaning and tone consistent with existing zh-CN style and
update any other non-English locale files if these keys were newly added in
en.ts.
🪄 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: 2ca39024-bcf0-4669-87cb-29cc72a36105
📒 Files selected for processing (14)
app/src/lib/i18n/ar.tsapp/src/lib/i18n/bn.tsapp/src/lib/i18n/de.tsapp/src/lib/i18n/es.tsapp/src/lib/i18n/fr.tsapp/src/lib/i18n/hi.tsapp/src/lib/i18n/id.tsapp/src/lib/i18n/it.tsapp/src/lib/i18n/ko.tsapp/src/lib/i18n/pl.tsapp/src/lib/i18n/pt.tsapp/src/lib/i18n/ru.tsapp/src/lib/i18n/zh-CN.tsapp/src/pages/Intelligence.tsx
✅ Files skipped from review due to trivial changes (6)
- app/src/lib/i18n/pt.ts
- app/src/lib/i18n/ko.ts
- app/src/lib/i18n/es.ts
- app/src/lib/i18n/bn.ts
- app/src/lib/i18n/it.ts
- app/src/lib/i18n/ar.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- app/src/pages/Intelligence.tsx
Rust Core Coverage failed in its post-step with "No space left on device" during cache save — a runner-disk infra flake unrelated to this TS-only PR (Rust is byte-identical to main; the same flake hit tinyhumansai#2978 previously). Co-Authored-By: Claude Opus 4.7 <[email protected]>
…rmation) A new read-only "Specialisation" tab. The vocabulary lens (tinyhumansai#17 Predicate Diversity) tells you how varied the global predicate set is. The thickness lens (tinyhumansai#18 Predicate Bundles) tells you which entity pairs share many predicates. The frequency lens (tinyhumansai#7 Relationship Types) tells you which predicates dominate overall. None of them answers the question this lens does: how much does knowing the SUBJECT predict which predicate it will use? And which entities speak in a specialised vocabulary versus a generalist one? Engine (pure, deterministic — no React/RPC/clock/RNG): - Global: I(S; P) in bits with canonical-order summation; H(S) and H(P); normalisedMI = I / min(H(S), H(P)). - Per-subject: specialisation = 1 - H(P|S=s)/log2(D_s) in [0,1] (1 when D_s <= 1 — single-predicate subject is maximally specialised by convention), plus dominantPredicate (tie-broken by predicate ASC) and dominantPredicateShare. - All p·log2(p) and joint·log2 ratio sums walk pairs in canonical (sortedSubjects, sortedPredicates) order so the result is byte-identical regardless of relation insertion order (lesson from tinyhumansai#2978 Cohesion's float-order bug). Selected from the prior loop-19 design workflow's runner-up (8.35/10 — a genuinely new lens not covered by any of the 19 shipped features). Adds ZERO new core surface: composes the already-shipped memoryGraphQuery / memoryListNamespaces wrappers. Container/presentational split with a monotonic request-token race guard for load-on-mount; i18n across all 13 locales. Co-Authored-By: Claude Opus 4.7 <[email protected]>
6610563 to
f66d58c
Compare
Rust Core Coverage failed in its post-step with "No space left on device" during cache save — a runner-disk infra flake unrelated to this TS-only PR (Rust is byte-identical to main; the same flake hit tinyhumansai#2978 previously). Co-Authored-By: Claude Opus 4.7 <[email protected]>
A new read-only "Cohesion" tab for the Intelligence view: clustering- coefficient analysis of the memory knowledge graph. Treating the triples as an undirected simple graph, it surfaces a structural signal the centrality and frequency lenses cannot — BROKERS: entities whose neighbours are not connected to each other, i.e. the sole link holding otherwise-separate clusters together. Engine (pure, deterministic — no React/RPC/clock/RNG): - per-node local clustering coefficient C(v) = 2·triangles / (deg·(deg-1)), - triangleCount, averageClustering (mean over degree>=2 nodes), and transitivity (global clustering coefficient = 3·triangles / connected-triples), - findBrokers() ranks the loosest-neighbourhood entities (structural holes). Adds ZERO new core surface: composes the already-shipped memoryGraphQuery / memoryListNamespaces JSON-RPC wrappers and delegates all math to the engine. Container/presentational split with a monotonic request-token race guard for load-on-mount; i18n across all 13 locales. Co-Authored-By: Claude Opus 4.7 <[email protected]>
The Rust Core Coverage job failed in its post-run cache-save step with "No space left on device" — a runner-disk infra flake unrelated to this TS-only change (Rust is byte-identical to main; all other Rust jobs passed). Empty commit to re-run on a fresh runner. Co-Authored-By: Claude Opus 4.7 <[email protected]>
Rust Core Coverage failed on an unrelated, flaky upstream Rust unit test (openhuman::memory_tools::tools::put::tests::execute_defaults_unknown_priority_to_normal — a non-deterministic "namespace/key cannot contain personal identifiers" rule). This PR is TS-only; Rust is byte-identical to main and the same job passes on sibling PRs. Re-running on a fresh runner. Co-Authored-By: Claude Opus 4.7 <[email protected]>
f66d58c to
2b4efcc
Compare
The branch rebase resolved the Intelligence.tsx conflict by taking main's tab set (associations/freshness/timeline/path) and dropping the cohesion wiring, leaving GraphCohesionTab/Panel/engine/api as dead code that was never imported or rendered. Restore the import, the 'cohesion' tab union member, the pill entry, and the render branch (placed after 'centrality', its sibling graph-analysis lens).
| nodeCount: adjacency.size, | ||
| edgeCount: edgeDegreeSum / 2, | ||
| triangleCount: closedTripleSum / 3, | ||
| averageClustering: clusterableCount === 0 ? 0 : clusteringSum / clusterableCount, |
There was a problem hiding this comment.
Re: the MAJOR "cross-platform floating-point non-associativity" finding — I looked into this and don't think a change is warranted:
- JS arithmetic is architecture-deterministic. ECMAScript pins
Numberto IEEE-754 binary64 with round-ties-to-even and no FMA/extended-precision contraction, soa + bon two doubles yields a bit-identical result on x86-64 and ARM (Apple Silicon/iOS). The x87-extended-precision / FMA-contraction divergence the comment describes is a C/C++ concern, not a JS one. The in-file determinism contract (canonical-order summation) already covers the only real source of drift — summand order. - The displayed value is rounded anyway.
GraphCohesionPanel.tsxrendersaverageClustering.toFixed(2)(2 dp), so even a hypothetical sub-ULP difference could never surface to the user.
Rounding inside the engine return object would also conflict with the byte-identical-determinism unit tests. Leaving as-is — happy to revisit if you have a concrete repro of divergence.
| * 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[] { |
There was a problem hiding this comment.
Re: the MINOR "findBrokers() has no default result cap" finding — this is already addressed in the current code: the signature is findBrokers(result: CohesionResult, limit = 25), so an omitted limit caps the result at 25 rather than materializing the full ranked list. The review likely predates that default. No change needed.
Rust Core Coverage failed in its post-step with "No space left on device" during cache save — a runner-disk infra flake unrelated to this TS-only PR (Rust is byte-identical to main; the same flake hit tinyhumansai#2978 previously). Co-Authored-By: Claude Opus 4.7 <[email protected]>
…rmation) A new read-only "Specialisation" tab. The vocabulary lens (tinyhumansai#17 Predicate Diversity) tells you how varied the global predicate set is. The thickness lens (tinyhumansai#18 Predicate Bundles) tells you which entity pairs share many predicates. The frequency lens (tinyhumansai#7 Relationship Types) tells you which predicates dominate overall. None of them answers the question this lens does: how much does knowing the SUBJECT predict which predicate it will use? And which entities speak in a specialised vocabulary versus a generalist one? Engine (pure, deterministic — no React/RPC/clock/RNG): - Global: I(S; P) in bits with canonical-order summation; H(S) and H(P); normalisedMI = I / min(H(S), H(P)). - Per-subject: specialisation = 1 - H(P|S=s)/log2(D_s) in [0,1] (1 when D_s <= 1 — single-predicate subject is maximally specialised by convention), plus dominantPredicate (tie-broken by predicate ASC) and dominantPredicateShare. - All p·log2(p) and joint·log2 ratio sums walk pairs in canonical (sortedSubjects, sortedPredicates) order so the result is byte-identical regardless of relation insertion order (lesson from tinyhumansai#2978 Cohesion's float-order bug). Selected from the prior loop-19 design workflow's runner-up (8.35/10 — a genuinely new lens not covered by any of the 19 shipped features). Adds ZERO new core surface: composes the already-shipped memoryGraphQuery / memoryListNamespaces wrappers. Container/presentational split with a monotonic request-token race guard for load-on-mount; i18n across all 13 locales. Co-Authored-By: Claude Opus 4.7 <[email protected]>
Rust Core Coverage failed in its post-step with "No space left on device" during cache save — a runner-disk infra flake unrelated to this TS-only PR (Rust is byte-identical to main; the same flake hit tinyhumansai#2978 previously). Co-Authored-By: Claude Opus 4.7 <[email protected]>
…rmation) A new read-only "Specialisation" tab. The vocabulary lens (tinyhumansai#17 Predicate Diversity) tells you how varied the global predicate set is. The thickness lens (tinyhumansai#18 Predicate Bundles) tells you which entity pairs share many predicates. The frequency lens (tinyhumansai#7 Relationship Types) tells you which predicates dominate overall. None of them answers the question this lens does: how much does knowing the SUBJECT predict which predicate it will use? And which entities speak in a specialised vocabulary versus a generalist one? Engine (pure, deterministic — no React/RPC/clock/RNG): - Global: I(S; P) in bits with canonical-order summation; H(S) and H(P); normalisedMI = I / min(H(S), H(P)). - Per-subject: specialisation = 1 - H(P|S=s)/log2(D_s) in [0,1] (1 when D_s <= 1 — single-predicate subject is maximally specialised by convention), plus dominantPredicate (tie-broken by predicate ASC) and dominantPredicateShare. - All p·log2(p) and joint·log2 ratio sums walk pairs in canonical (sortedSubjects, sortedPredicates) order so the result is byte-identical regardless of relation insertion order (lesson from tinyhumansai#2978 Cohesion's float-order bug). Selected from the prior loop-19 design workflow's runner-up (8.35/10 — a genuinely new lens not covered by any of the 19 shipped features). Adds ZERO new core surface: composes the already-shipped memoryGraphQuery / memoryListNamespaces wrappers. Container/presentational split with a monotonic request-token race guard for load-on-mount; i18n across all 13 locales. Co-Authored-By: Claude Opus 4.7 <[email protected]>
…rmation) A new read-only "Specialisation" tab. The vocabulary lens (tinyhumansai#17 Predicate Diversity) tells you how varied the global predicate set is. The thickness lens (tinyhumansai#18 Predicate Bundles) tells you which entity pairs share many predicates. The frequency lens (tinyhumansai#7 Relationship Types) tells you which predicates dominate overall. None of them answers the question this lens does: how much does knowing the SUBJECT predict which predicate it will use? And which entities speak in a specialised vocabulary versus a generalist one? Engine (pure, deterministic — no React/RPC/clock/RNG): - Global: I(S; P) in bits with canonical-order summation; H(S) and H(P); normalisedMI = I / min(H(S), H(P)). - Per-subject: specialisation = 1 - H(P|S=s)/log2(D_s) in [0,1] (1 when D_s <= 1 — single-predicate subject is maximally specialised by convention), plus dominantPredicate (tie-broken by predicate ASC) and dominantPredicateShare. - All p·log2(p) and joint·log2 ratio sums walk pairs in canonical (sortedSubjects, sortedPredicates) order so the result is byte-identical regardless of relation insertion order (lesson from tinyhumansai#2978 Cohesion's float-order bug). Selected from the prior loop-19 design workflow's runner-up (8.35/10 — a genuinely new lens not covered by any of the 19 shipped features). Adds ZERO new core surface: composes the already-shipped memoryGraphQuery / memoryListNamespaces wrappers. Container/presentational split with a monotonic request-token race guard for load-on-mount; i18n across all 13 locales. Co-Authored-By: Claude Opus 4.7 <[email protected]>
Co-authored-by: Claude Opus 4.7 <[email protected]> Co-authored-by: Steven Enamakel <[email protected]>
Summary
Adds a new read-only "Cohesion" tab to the Intelligence view: clustering-coefficient analysis of the memory knowledge graph. Where the Centrality lens answers "which entities are important", this lens answers "how tightly knit is the neighbourhood around each entity" — and surfaces a structural signal neither centrality nor a frequency sort can reveal: brokers.
A broker (structural hole) is an entity whose neighbours are not connected to each other — it's the sole link holding otherwise-separate clusters together (local clustering ≈ 0). These are the single points of failure / brokerage opportunities in the user's accumulated memory.
Design
lib/memory/graphCohesion.ts): treats the(subject)-[predicate]->(object)triples as an undirected simple graph (direction dropped, parallel edges collapsed, self-loops dropped) and computes:C(v) = 2·triangles / (deg·(deg-1)),Cover degree≥2 nodes), and transitivity (the global clustering coefficient,3·triangles / connected-triples),findBrokers()— ranks the loosest-neighbourhood entities.averageClusteringis summed in canonical (sorted) order so it is byte-identical across input permutations despite IEEE-754 non-associativity.memoryGraphQuery/memoryListNamespacesJSON-RPC wrappers. Read-only — recomputed live from the graph, never persisted.Test plan
vitest— 33 tests (engine: empty/triangle/path/4-cycle/star/diamond fixtures with hand-computed clustering, triangle, avg & transitivity values; self-loop drop; parallel-edge & direction collapse; malformed-row drop; no case-folding; byte-identical determinism across permutations; broker ranking & limits — plus api facade, panel states + broker badge + no-brokers note, container load/namespace-requery/error)tsc --noEmit— cleaneslint— 0 errorsprettier --check— clean🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
UX
Localization