@andespindola/brainlink 0.1.0-beta.89 → 0.1.0-beta.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -602,7 +602,7 @@ The graph UI shows:
602
602
  - WebGL node and edge acceleration when supported, falling back to Canvas 2D without changing graph behavior
603
603
  - compact macro-to-micro density progression so reset keeps the graph mass oriented and zoom-in separates local neighborhoods progressively
604
604
  - graph camera treats hub-centered navigation as structural only when the hub is dominant; diffuse stress graphs reset and zoom around the full graph mass
605
- - graph LOD progression: graphs up to 1000 notes render directly; larger graphs use a compact memory-hub-centered mesh of connected 1000-note points, zoom-in spreads only focused clusters into 250-note and 60-note subgraphs with aggregated real links, keeps parent points during expansion to avoid visual jumps, then progressively raises the focused node budget so local areas keep nearby notes and links visible
605
+ - graph LOD progression: graphs up to 1000 notes render directly; larger graphs use a compact memory-hub-centered mesh of connected 1000-note points, zoom-in spreads only focused clusters into 250-note and 60-note subgraphs with aggregated real links plus local sibling mesh links, keeps parent points during expansion to avoid visual jumps, then progressively raises the focused node budget so local areas keep nearby notes and links visible
606
606
 
607
607
  The server indexes before starting by default. Use `--no-index` to skip that step:
608
608
 
@@ -26,6 +26,7 @@ const ecosystemActivationNodeThreshold = 1000
26
26
  const ecosystemGroupSizes = [1000, 250, 60]
27
27
  const ecosystemClusterEdgeLimit = 520
28
28
  const ecosystemHubEdgeLimit = 120
29
+ const ecosystemSiblingEdgeLimit = 180
29
30
  const ecosystemClusterScaleThreshold = 0.32
30
31
  const ecosystemSubgraphScaleThreshold = 0.18
31
32
  const ecosystemMicroScaleThreshold = 0.08
@@ -979,20 +980,74 @@ const selectHierarchicalEcosystemClusters = viewport => {
979
980
  ]
980
981
  }
981
982
 
983
+ const ecosystemSiblingEdgesForClusters = (clusters, existingEdges) => {
984
+ const byParent = new Map()
985
+ for (let index = 0; index < clusters.length; index += 1) {
986
+ const cluster = clusters[index]
987
+ if (cluster.isHub || !cluster.parentId) {
988
+ continue
989
+ }
990
+ const siblings = byParent.get(cluster.parentId)
991
+ if (siblings) {
992
+ siblings.push(cluster)
993
+ } else {
994
+ byParent.set(cluster.parentId, [cluster])
995
+ }
996
+ }
997
+
998
+ const edges = []
999
+ for (const siblings of byParent.values()) {
1000
+ const ordered = [...siblings]
1001
+ .sort((left, right) => Math.atan2(left.y - (left.parentY ?? 0), left.x - (left.parentX ?? 0)) - Math.atan2(right.y - (right.parentY ?? 0), right.x - (right.parentX ?? 0)))
1002
+ for (let index = 0; index < ordered.length && edges.length < ecosystemSiblingEdgeLimit; index += 1) {
1003
+ const sourceCluster = ordered[index]
1004
+ const targetCluster = ordered[(index + 1) % ordered.length]
1005
+ if (!targetCluster || sourceCluster.id === targetCluster.id) {
1006
+ continue
1007
+ }
1008
+ const orderedIds = sourceCluster.id < targetCluster.id
1009
+ ? [sourceCluster.id, targetCluster.id]
1010
+ : [targetCluster.id, sourceCluster.id]
1011
+ const key = orderedIds.join(':')
1012
+ if (existingEdges.has(key)) {
1013
+ continue
1014
+ }
1015
+ const edge = {
1016
+ id: key,
1017
+ sourceCluster,
1018
+ targetCluster,
1019
+ weight: 0.7,
1020
+ inferred: true
1021
+ }
1022
+ existingEdges.set(key, edge)
1023
+ edges.push(edge)
1024
+ }
1025
+ }
1026
+
1027
+ return edges
1028
+ }
1029
+
982
1030
  const ecosystemEdgesForClusters = clusters => {
983
1031
  const clusterById = new Map(clusters.map(cluster => [cluster.id, cluster]))
984
1032
  const clusterIds = new Set(clusterById.keys())
985
- const levelBySize = new Map()
1033
+ const levelsBySize = []
986
1034
  for (let index = 0; index < clusters.length; index += 1) {
987
1035
  const cluster = clusters[index]
988
1036
  if (!cluster.size || cluster.isHub) continue
989
- levelBySize.set(cluster.size, state.ecosystemNodeClusterBySize.get(cluster.size) ?? new Map())
1037
+ if (!levelsBySize.some(level => level.size === cluster.size)) {
1038
+ levelsBySize.push({
1039
+ size: cluster.size,
1040
+ lookup: state.ecosystemNodeClusterBySize.get(cluster.size) ?? new Map()
1041
+ })
1042
+ }
990
1043
  }
1044
+ levelsBySize.sort((left, right) => left.size - right.size)
991
1045
  const resolveClusterForNode = nodeId => {
992
1046
  if (state.ecosystemHubCluster?.nodeIds.includes(nodeId) && clusterIds.has(state.ecosystemHubCluster.id)) {
993
1047
  return state.ecosystemHubCluster
994
1048
  }
995
- for (const [size, lookup] of levelBySize) {
1049
+ for (let index = 0; index < levelsBySize.length; index += 1) {
1050
+ const lookup = levelsBySize[index].lookup
996
1051
  const cluster = lookup.get(nodeId)
997
1052
  if (cluster && clusterIds.has(cluster.id)) {
998
1053
  return clusterById.get(cluster.id) ?? cluster
@@ -1028,6 +1083,7 @@ const ecosystemEdgesForClusters = clusters => {
1028
1083
  })
1029
1084
  }
1030
1085
 
1086
+ ecosystemSiblingEdgesForClusters(clusters, edgeByClusterPair)
1031
1087
  const edges = Array.from(edgeByClusterPair.values())
1032
1088
  .sort((left, right) => right.weight - left.weight)
1033
1089
  .slice(0, ecosystemClusterEdgeLimit)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.89",
3
+ "version": "0.1.0-beta.90",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",