@unson/brainbase-mcp 0.10.0 → 0.10.1
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/package.json +2 -2
- package/ui/project-graph-entry.js +36 -33
- package/ui/project-graph.js +100 -5
- package/ui/project-workspace.js +9 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "Local-first Brainbase judgment OS that lets supported AI agents reuse a user's approved goals, priorities, decisions, and judgment criteria.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -336,5 +336,5 @@
|
|
|
336
336
|
"engines": {
|
|
337
337
|
"node": ">=20"
|
|
338
338
|
},
|
|
339
|
-
"gitHead": "
|
|
339
|
+
"gitHead": "3fab56acebc9266f2bd256cc1795a3d7f550c311"
|
|
340
340
|
}
|
|
@@ -10,7 +10,9 @@ import { MultiDirectedGraph, Sigma } from './project-graph-vendor.js';
|
|
|
10
10
|
import {
|
|
11
11
|
PROJECT_GRAPH_TYPE_COLORS,
|
|
12
12
|
createProjectGraphModel,
|
|
13
|
+
projectGraphEdgePresentation,
|
|
13
14
|
projectGraphNeighborIds,
|
|
15
|
+
projectGraphNodePresentation,
|
|
14
16
|
} from './project-graph.js';
|
|
15
17
|
|
|
16
18
|
const DEFAULT_EDGE_COLOR = '#cbd5e1';
|
|
@@ -85,12 +87,6 @@ function makeGraph(model) {
|
|
|
85
87
|
return graph;
|
|
86
88
|
}
|
|
87
89
|
|
|
88
|
-
function edgeTouches(edge, nodeId, neighborIds) {
|
|
89
|
-
if (!nodeId) return false;
|
|
90
|
-
return edge.source === nodeId || edge.target === nodeId ||
|
|
91
|
-
(neighborIds.has(edge.source) && neighborIds.has(edge.target));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
90
|
/**
|
|
95
91
|
* Mounts one project graph and returns an idempotent resource handle.
|
|
96
92
|
*
|
|
@@ -153,46 +149,53 @@ export function mountProjectGraph(container, options = {}) {
|
|
|
153
149
|
const base = { ...data };
|
|
154
150
|
const neighbors = getNeighbors();
|
|
155
151
|
if (!selectedId) return base;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
...base,
|
|
168
|
-
color: base.baseColor || PROJECT_GRAPH_TYPE_COLORS.unknown,
|
|
169
|
-
size: Math.max(9, (base.size || 9) + 1),
|
|
170
|
-
highlighted: true,
|
|
171
|
-
forceLabel: true,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
152
|
+
const presentation = projectGraphNodePresentation(
|
|
153
|
+
{ id: nodeId, size: base.size },
|
|
154
|
+
{ selectedId, neighborIds: neighbors },
|
|
155
|
+
);
|
|
156
|
+
const { state, ...visuals } = presentation;
|
|
157
|
+
if (state === 'selected') return { ...base, ...visuals, color: SELECTED_COLOR };
|
|
158
|
+
if (state === 'neighbor') return {
|
|
159
|
+
...base,
|
|
160
|
+
...visuals,
|
|
161
|
+
color: base.baseColor || PROJECT_GRAPH_TYPE_COLORS.unknown,
|
|
162
|
+
};
|
|
174
163
|
return {
|
|
175
164
|
...base,
|
|
165
|
+
...visuals,
|
|
176
166
|
color: UNRELATED_COLOR,
|
|
177
|
-
size: Math.max(5, (base.size || 9) * 0.72),
|
|
178
|
-
label: null,
|
|
179
|
-
forceLabel: false,
|
|
180
|
-
highlighted: false,
|
|
181
167
|
};
|
|
182
168
|
};
|
|
183
169
|
|
|
184
170
|
const edgeReducer = (edgeId, data) => {
|
|
185
171
|
const edge = edgeById.get(edgeId);
|
|
186
172
|
const base = { ...data };
|
|
187
|
-
if (!selectedId || !edge)
|
|
173
|
+
if (!selectedId || !edge) {
|
|
174
|
+
return {
|
|
175
|
+
...base,
|
|
176
|
+
label: null,
|
|
177
|
+
forceLabel: false,
|
|
178
|
+
color: base.baseColor || DEFAULT_EDGE_COLOR,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
188
181
|
const neighbors = getNeighbors();
|
|
189
|
-
|
|
190
|
-
|
|
182
|
+
const presentation = projectGraphEdgePresentation(edge, {
|
|
183
|
+
selectedId,
|
|
184
|
+
neighborIds: neighbors,
|
|
185
|
+
});
|
|
186
|
+
if (!presentation.relevant) {
|
|
187
|
+
return {
|
|
188
|
+
...base,
|
|
189
|
+
label: null,
|
|
190
|
+
forceLabel: false,
|
|
191
|
+
color: UNRELATED_EDGE_COLOR,
|
|
192
|
+
size: 0.5,
|
|
193
|
+
};
|
|
191
194
|
}
|
|
192
195
|
return {
|
|
193
196
|
...base,
|
|
194
|
-
label: edge.label
|
|
195
|
-
forceLabel:
|
|
197
|
+
label: presentation.showLabel ? edge.label : null,
|
|
198
|
+
forceLabel: presentation.forceLabel,
|
|
196
199
|
color: edge.color || DEFAULT_EDGE_COLOR,
|
|
197
200
|
size: 1.5,
|
|
198
201
|
};
|
package/ui/project-graph.js
CHANGED
|
@@ -24,6 +24,9 @@ export const PROJECT_GRAPH_TYPE_COLORS = Object.freeze({
|
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
const EMPTY_ARRAY = Object.freeze([]);
|
|
27
|
+
const MAX_SELECTED_NODE_SIZE = 18;
|
|
28
|
+
const MAX_NEIGHBOR_NODE_SIZE = 12;
|
|
29
|
+
const MAX_UNRELATED_NODE_SIZE = 10;
|
|
27
30
|
|
|
28
31
|
function isRecord(value) {
|
|
29
32
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -168,6 +171,81 @@ export function projectGraphNeighborIds(nodes, edges, selectedId) {
|
|
|
168
171
|
return neighbors;
|
|
169
172
|
}
|
|
170
173
|
|
|
174
|
+
function toIdSet(value) {
|
|
175
|
+
return value instanceof Set
|
|
176
|
+
? value
|
|
177
|
+
: new Set(Array.isArray(value) ? value.filter(Boolean) : EMPTY_ARRAY);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Returns the visual state for one node in a selected graph neighborhood.
|
|
182
|
+
*
|
|
183
|
+
* A selected node is the only node that bypasses Sigma's label collision
|
|
184
|
+
* avoidance. Neighbors keep their labels available to Sigma's normal label
|
|
185
|
+
* grid, while their size and hover state stay bounded in dense neighborhoods.
|
|
186
|
+
*/
|
|
187
|
+
export function projectGraphNodePresentation(node, { selectedId = null, neighborIds = EMPTY_ARRAY } = {}) {
|
|
188
|
+
const id = stringValue(node?.id);
|
|
189
|
+
const neighbors = toIdSet(neighborIds);
|
|
190
|
+
const state = id && id === selectedId
|
|
191
|
+
? 'selected'
|
|
192
|
+
: id && neighbors.has(id)
|
|
193
|
+
? 'neighbor'
|
|
194
|
+
: 'unrelated';
|
|
195
|
+
const baseSize = Math.min(finitePositive(node?.size, 9), MAX_SELECTED_NODE_SIZE);
|
|
196
|
+
|
|
197
|
+
if (state === 'selected') {
|
|
198
|
+
return {
|
|
199
|
+
state,
|
|
200
|
+
size: Math.min(MAX_SELECTED_NODE_SIZE, Math.max(10, baseSize + 3)),
|
|
201
|
+
forceLabel: true,
|
|
202
|
+
highlighted: true,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (state === 'neighbor') {
|
|
207
|
+
return {
|
|
208
|
+
state,
|
|
209
|
+
size: Math.min(MAX_NEIGHBOR_NODE_SIZE, Math.max(6, baseSize)),
|
|
210
|
+
forceLabel: false,
|
|
211
|
+
highlighted: false,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
state,
|
|
217
|
+
size: Math.min(MAX_UNRELATED_NODE_SIZE, Math.max(4, baseSize * 0.72)),
|
|
218
|
+
label: null,
|
|
219
|
+
forceLabel: false,
|
|
220
|
+
highlighted: false,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Returns the label policy for one edge in a selected graph neighborhood.
|
|
226
|
+
* Dense neighborhoods omit edge labels; relation details remain available
|
|
227
|
+
* in the selected record panel. Small neighborhoods show relevant labels.
|
|
228
|
+
*/
|
|
229
|
+
export function projectGraphEdgePresentation(edge, { selectedId = null, neighborIds = EMPTY_ARRAY } = {}) {
|
|
230
|
+
const neighbors = toIdSet(neighborIds);
|
|
231
|
+
const relevant = Boolean(
|
|
232
|
+
selectedId && edge && (
|
|
233
|
+
edge.source === selectedId ||
|
|
234
|
+
edge.target === selectedId ||
|
|
235
|
+
(neighbors.has(edge.source) && neighbors.has(edge.target))
|
|
236
|
+
),
|
|
237
|
+
);
|
|
238
|
+
// Sigma's edge-label renderer does not apply the same collision grid as
|
|
239
|
+
// node labels. Hide labels for a dense neighborhood and leave the relation
|
|
240
|
+
// available through selection/details instead of painting a radial stack.
|
|
241
|
+
const denseNeighborhood = neighbors.size > 12;
|
|
242
|
+
return {
|
|
243
|
+
relevant,
|
|
244
|
+
showLabel: relevant && !denseNeighborhood && Boolean(stringValue(edge?.label)),
|
|
245
|
+
forceLabel: false,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
171
249
|
/**
|
|
172
250
|
* Computes a stable clustered layout. The selected node is the anchor when
|
|
173
251
|
* it exists; its direct neighbours form the first ring and the remaining
|
|
@@ -188,11 +266,28 @@ export function computeProjectGraphLayout(nodes, edges = EMPTY_ARRAY, { selected
|
|
|
188
266
|
const neighbors = list
|
|
189
267
|
.filter((node) => neighborIds.has(node.id))
|
|
190
268
|
.sort(compareNodes);
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
269
|
+
// Keep high-degree selections readable by spreading neighbors over stable
|
|
270
|
+
// concentric rings. A single ring turns a person with dozens of relations
|
|
271
|
+
// into a dense necklace even when Sigma's label grid is working correctly.
|
|
272
|
+
const neighborRingCount = neighbors.length ? Math.ceil(neighbors.length / 24) : 0;
|
|
273
|
+
const neighborRingRadius = 2.6;
|
|
274
|
+
const neighborRingGap = 1.8;
|
|
275
|
+
let neighborOffset = 0;
|
|
276
|
+
for (let ringIndex = 0; ringIndex < neighborRingCount; ringIndex += 1) {
|
|
277
|
+
const remaining = neighbors.length - neighborOffset;
|
|
278
|
+
const ringsLeft = neighborRingCount - ringIndex;
|
|
279
|
+
const ringSize = Math.ceil(remaining / ringsLeft);
|
|
280
|
+
const radius = neighborRingRadius + ringIndex * neighborRingGap;
|
|
281
|
+
for (let index = 0; index < ringSize; index += 1) {
|
|
282
|
+
const angle = -Math.PI / 2 + (2 * Math.PI * index) / Math.max(1, ringSize) + ringIndex * 0.19;
|
|
283
|
+
const node = neighbors[neighborOffset + index];
|
|
284
|
+
positions[node.id] = { x: Math.cos(angle) * radius, y: Math.sin(angle) * radius };
|
|
285
|
+
}
|
|
286
|
+
neighborOffset += ringSize;
|
|
287
|
+
}
|
|
288
|
+
const neighborRadius = neighborRingCount
|
|
289
|
+
? neighborRingRadius + (neighborRingCount - 1) * neighborRingGap
|
|
290
|
+
: 1.5;
|
|
196
291
|
|
|
197
292
|
const remaining = list.filter((node) => !positions[node.id]);
|
|
198
293
|
const groups = new Map();
|
package/ui/project-workspace.js
CHANGED
|
@@ -445,8 +445,13 @@ export function createProjectKnowledgeWorkspace({
|
|
|
445
445
|
const payload = projectDetailPayload(detail);
|
|
446
446
|
const project = payload?.project;
|
|
447
447
|
if (!project) throw new TypeError('project detail is required');
|
|
448
|
-
|
|
449
|
-
|
|
448
|
+
// Keep the project-bound projection separate from the mutable exploration
|
|
449
|
+
// graph. Reading a related entity may reveal arbitrary neighboring records,
|
|
450
|
+
// but those records are not evidence that they belong to this project.
|
|
451
|
+
const overviewEntities = graphEntityMap(payload);
|
|
452
|
+
const overviewEdges = graphEvidenceEdges(payload);
|
|
453
|
+
const entities = new Map(overviewEntities);
|
|
454
|
+
const edges = [...overviewEdges];
|
|
450
455
|
let destroyed = false;
|
|
451
456
|
let selectedId = project.id;
|
|
452
457
|
let activeTab = 'overview';
|
|
@@ -576,7 +581,7 @@ export function createProjectKnowledgeWorkspace({
|
|
|
576
581
|
['目的', textOrNull(selectedEntity.goal)],
|
|
577
582
|
['有効期間', validityText(selectedEntity.validFrom, selectedEntity.validTo)],
|
|
578
583
|
]));
|
|
579
|
-
panel.append(makeElement(doc, 'h4', { text: 'このプロジェクトとの関係' }), relationEvidence(doc,
|
|
584
|
+
panel.append(makeElement(doc, 'h4', { text: 'このプロジェクトとの関係' }), relationEvidence(doc, overviewEdges, selectedId, overviewEntities, payload.asOf, selectAndExplore));
|
|
580
585
|
if (selectedId !== project.id && !fetched) {
|
|
581
586
|
const failed = read?.state === 'failed';
|
|
582
587
|
panel.append(workspaceNotice(doc, {
|
|
@@ -699,7 +704,7 @@ export function createProjectKnowledgeWorkspace({
|
|
|
699
704
|
teardownGraph();
|
|
700
705
|
tabs.replaceChildren(tabButton('概要', 'overview'), tabButton('情報を探す', 'graph'));
|
|
701
706
|
panels.replaceChildren();
|
|
702
|
-
const overview = renderOverview(doc, payload, context,
|
|
707
|
+
const overview = renderOverview(doc, payload, context, overviewEdges, overviewEntities, selectAndExplore);
|
|
703
708
|
overview.id = 'bb-pkw-panel-overview';
|
|
704
709
|
overview.hidden = activeTab !== 'overview';
|
|
705
710
|
panels.append(overview);
|