@unson/brainbase-mcp 0.2.4 → 0.3.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/README.md +17 -12
- package/dist/canonical-edge-builder.d.ts +11 -0
- package/dist/canonical-edge-builder.js +35 -0
- package/dist/canonical-graph.d.ts +9 -0
- package/dist/canonical-graph.js +174 -0
- package/dist/cli.js +134 -32
- package/dist/connected-onboarding.js +45 -11
- package/dist/entity-resolution.d.ts +111 -0
- package/dist/entity-resolution.js +381 -0
- package/dist/graph-diagnosis.d.ts +24 -0
- package/dist/graph-diagnosis.js +129 -0
- package/dist/guided-onboarding.js +4 -4
- package/dist/import-extract.d.ts +8 -3
- package/dist/import-extract.js +142 -17
- package/dist/onboarding.js +7 -5
- package/dist/ontology-migration.d.ts +30 -0
- package/dist/ontology-migration.js +339 -0
- package/dist/ontology-ssot.js +2 -2
- package/dist/ontology.d.ts +101 -3
- package/dist/ontology.js +64 -8
- package/dist/operationalization.js +6 -6
- package/dist/projects.d.ts +3 -1
- package/dist/projects.js +40 -0
- package/dist/relation-registry.d.ts +11 -0
- package/dist/relation-registry.js +66 -0
- package/dist/server.d.ts +86 -2
- package/dist/server.js +158 -9
- package/dist/skills.js +9 -6
- package/dist/ssot.d.ts +16 -0
- package/dist/ssot.js +68 -18
- package/dist/templates.d.ts +110 -2
- package/dist/templates.js +74 -5
- package/dist/tools.d.ts +6 -2
- package/dist/tools.js +184 -147
- package/dist/types.d.ts +52 -1
- package/package.json +4 -3
package/dist/tools.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
import { isActiveAt } from './canonical-graph.js';
|
|
1
2
|
import { buildOperationalizationPlan } from './operationalization.js';
|
|
2
|
-
|
|
3
|
+
import { getCanonicalRelation } from './relation-registry.js';
|
|
4
|
+
export function getContext(os, options = {}) {
|
|
3
5
|
const selfEntries = os.personalKg.filter((entry) => entry.type === 'self' || entry.type === 'value' || entry.type === 'judgment');
|
|
4
6
|
const workEntries = os.personalKg.filter((entry) => entry.type === 'work' || entry.type === 'experience');
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
role: readMetadataString(entity.metadata, 'role'),
|
|
7
|
+
const scoped = scopedRecords(os, options);
|
|
8
|
+
const visibleIds = os.graph.version === 2 ? new Set(scoped.records.map((record) => record.entity.id)) : undefined;
|
|
9
|
+
const projects = os.graph.entities
|
|
10
|
+
.filter((entity) => entity.type === 'project' && (!visibleIds || visibleIds.has(entity.id)))
|
|
11
|
+
.map((entity) => ({
|
|
12
|
+
id: entity.id, name: entity.name, summary: entity.summary,
|
|
13
|
+
goal: readMetadataString(entity.metadata, 'goal'), status: readMetadataString(entity.metadata, 'status'), role: readMetadataString(entity.metadata, 'role'),
|
|
12
14
|
sources: Array.isArray(entity.metadata?.sources) ? entity.metadata.sources : [],
|
|
13
15
|
taskSources: Array.isArray(entity.metadata?.taskSources) ? entity.metadata.taskSources : [],
|
|
14
16
|
decisionPrinciples: Array.isArray(entity.metadata?.decisionPrinciples) ? entity.metadata.decisionPrinciples : []
|
|
@@ -17,175 +19,210 @@ export function getContext(os) {
|
|
|
17
19
|
owner: os.graph.owner ?? {},
|
|
18
20
|
self: selfEntries.map((entry) => entry.text),
|
|
19
21
|
projects,
|
|
20
|
-
work: [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
],
|
|
24
|
-
relationships: os.relationships.relationships.map((relationship) => ({
|
|
25
|
-
person: relationship.person,
|
|
26
|
-
role: relationship.role,
|
|
27
|
-
context: relationship.context
|
|
28
|
-
})),
|
|
29
|
-
decisions: os.decisions.map((decision) => ({
|
|
30
|
-
title: decision.title,
|
|
31
|
-
decision: decision.decision,
|
|
32
|
-
rationale: decision.rationale
|
|
33
|
-
})),
|
|
22
|
+
work: [...projects.map((project) => project.summary ? `${project.name}: ${project.summary}` : project.name), ...workEntries.map((entry) => entry.text)],
|
|
23
|
+
relationships: os.relationships.relationships.map((relationship) => ({ person: relationship.person, role: relationship.role, context: relationship.context })),
|
|
24
|
+
decisions: os.decisions.map((decision) => ({ title: decision.title, decision: decision.decision, rationale: decision.rationale })),
|
|
34
25
|
canonicalFiles: ['graph.json', 'personal-kg.jsonl', 'relationships.json', 'decisions.jsonl'],
|
|
35
|
-
note: 'Canonical local SSOT data is preferred over raw sources.'
|
|
26
|
+
note: 'Canonical local SSOT data is preferred over raw sources.',
|
|
27
|
+
...(os.graph.version === 2 ? { canonicalGraph: {
|
|
28
|
+
schemaVersion: 2,
|
|
29
|
+
ontology: os.graph.ontology,
|
|
30
|
+
authority: 'local_graph',
|
|
31
|
+
asOf: scoped.asOf,
|
|
32
|
+
project: scoped.project ? { id: scoped.project.id, name: scoped.project.name } : null,
|
|
33
|
+
records: scoped.records.map(({ entity, relationPath }) => ({
|
|
34
|
+
...entity, canonicalEntityId: entity.id, recordClass: 'canonical', relationPath, authority: 'local_graph'
|
|
35
|
+
}))
|
|
36
|
+
} } : {})
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
39
|
export function listEntities(os, type) {
|
|
39
40
|
const graphEntities = os.graph.entities.filter((entity) => !type || entity.type === type);
|
|
40
|
-
const relationshipEntities = type && type !== 'relationship'
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
tags: relationship.tags
|
|
48
|
-
}));
|
|
49
|
-
const decisionEntities = type && type !== 'decision'
|
|
50
|
-
? []
|
|
51
|
-
: os.decisions.map((decision) => ({
|
|
52
|
-
id: decision.id,
|
|
53
|
-
type: 'decision',
|
|
54
|
-
name: decision.title,
|
|
55
|
-
summary: decision.decision,
|
|
56
|
-
tags: decision.tags
|
|
57
|
-
}));
|
|
58
|
-
return {
|
|
59
|
-
entities: [...graphEntities, ...relationshipEntities, ...decisionEntities]
|
|
60
|
-
};
|
|
41
|
+
const relationshipEntities = type && type !== 'relationship' ? [] : os.relationships.relationships.map((relationship) => ({
|
|
42
|
+
id: relationship.id, type: 'relationship', name: relationship.person, summary: relationship.context, tags: relationship.tags
|
|
43
|
+
}));
|
|
44
|
+
const decisionEntities = type && type !== 'decision' ? [] : os.decisions.map((decision) => ({
|
|
45
|
+
id: decision.id, type: 'decision', name: decision.title, summary: decision.decision, tags: decision.tags
|
|
46
|
+
}));
|
|
47
|
+
return { entities: [...graphEntities, ...relationshipEntities, ...decisionEntities] };
|
|
61
48
|
}
|
|
62
49
|
export function searchPersonalKg(os, query, limit = 10) {
|
|
63
50
|
return rank(os.personalKg.map((entry) => ({
|
|
64
|
-
source: 'personal-kg',
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
text: entry.text,
|
|
68
|
-
score: scoreText(query, [entry.id, entry.type, entry.text, ...(entry.tags ?? [])])
|
|
51
|
+
source: 'personal-kg', id: entry.id, title: entry.type, text: entry.text,
|
|
52
|
+
score: scoreText(query, [entry.id, entry.type, entry.text, ...(entry.tags ?? [])]),
|
|
53
|
+
recordClass: 'unresolved', authority: 'personal_kg'
|
|
69
54
|
})), limit);
|
|
70
55
|
}
|
|
71
|
-
export function searchAll(os, query, limit = 10) {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
56
|
+
export function searchAll(os, query, limit = 10, options = {}) {
|
|
57
|
+
const scoped = scopedRecords(os, options);
|
|
58
|
+
const graphRecords = os.graph.version === 2
|
|
59
|
+
? scoped.records
|
|
60
|
+
: os.graph.entities.map((entity) => ({ entity: entity, relationPath: [] }));
|
|
61
|
+
const graphResults = graphRecords.map(({ entity, relationPath }) => ({
|
|
62
|
+
source: 'graph', id: entity.id, title: entity.name,
|
|
76
63
|
text: [entity.summary ?? entity.name, metadataText(entity.metadata)].filter(Boolean).join('\n'),
|
|
77
|
-
score: scoreText(query, [entity.id, entity.type, entity.name, entity.summary ?? '', metadataText(entity.metadata), ...(entity.tags ?? [])])
|
|
64
|
+
score: scoreText(query, [entity.id, entity.type, entity.name, ...(entity.aliases ?? []), entity.summary ?? '', metadataText(entity.metadata), ...(entity.tags ?? [])]),
|
|
65
|
+
canonicalEntityId: entity.id, recordClass: 'canonical', relationPath, authority: 'local_graph'
|
|
78
66
|
}));
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
67
|
+
const visibleIds = new Set(scoped.records.map((record) => record.entity.id));
|
|
68
|
+
const targets = os.graph.version === 2 ? projectionTargets(os.graph, scoped.asOf, visibleIds) : undefined;
|
|
69
|
+
const relationshipResults = os.relationships.relationships.map((relationship) => {
|
|
70
|
+
const projectionOf = targets?.person.get(normalize(relationship.person));
|
|
71
|
+
return {
|
|
72
|
+
source: 'relationships', id: relationship.id, title: relationship.person, text: relationship.context,
|
|
73
|
+
score: scoreText(query, [relationship.id, relationship.person, relationship.role ?? '', relationship.context, ...(relationship.tags ?? [])]),
|
|
74
|
+
...(projectionOf ? { canonicalEntityId: projectionOf, projectionOf } : {}),
|
|
75
|
+
recordClass: projectionOf ? 'projection' : 'unresolved', authority: 'legacy_relationships'
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
const decisionResults = os.decisions.map((decision) => {
|
|
79
|
+
const projectionOf = targets?.decision.get(decision.id) ?? targets?.decision.get(normalize(decision.title));
|
|
80
|
+
return {
|
|
81
|
+
source: 'decisions', id: decision.id, title: decision.title,
|
|
82
|
+
text: [decision.decision, decision.rationale ?? ''].filter(Boolean).join('\n'),
|
|
83
|
+
score: scoreText(query, [decision.id, decision.title, decision.decision, decision.rationale ?? '', ...(decision.tags ?? [])]),
|
|
84
|
+
...(projectionOf ? { canonicalEntityId: projectionOf, projectionOf } : {}),
|
|
85
|
+
recordClass: projectionOf ? 'projection' : 'unresolved', authority: 'legacy_decisions'
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
const additional = [...relationshipResults, ...decisionResults, ...(options.project ? [] : searchPersonalKg(os, query, limit))]
|
|
89
|
+
.filter((result) => !options.project || Boolean(result.projectionOf));
|
|
90
|
+
const canonicalById = new Map(graphResults.map((result) => [result.id, result]));
|
|
91
|
+
const retained = [];
|
|
92
|
+
for (const result of additional) {
|
|
93
|
+
const canonical = result.projectionOf ? canonicalById.get(result.projectionOf) : undefined;
|
|
94
|
+
if (canonical) {
|
|
95
|
+
canonical.score = Math.max(canonical.score, result.score);
|
|
96
|
+
if (result.source === 'relationships' || result.source === 'decisions') {
|
|
97
|
+
canonical.projectionSources = [...new Set([...(canonical.projectionSources ?? []), result.source])];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else
|
|
101
|
+
retained.push(result);
|
|
102
|
+
}
|
|
103
|
+
return rank([...graphResults, ...retained], limit);
|
|
94
104
|
}
|
|
95
105
|
export function onboardingStatus(os) {
|
|
96
106
|
const seeded = {
|
|
97
107
|
self: Boolean(os.graph.owner?.name) || os.personalKg.some((entry) => entry.type === 'self'),
|
|
98
108
|
work: os.graph.entities.some((entity) => entity.type === 'project') || os.personalKg.some((entry) => entry.type === 'work'),
|
|
99
|
-
relationships: os.relationships.relationships.length > 0
|
|
109
|
+
relationships: os.relationships.relationships.length > 0 || (os.graph.version === 2 && os.graph.edges.length > 0)
|
|
100
110
|
};
|
|
101
|
-
const missing = Object.entries(seeded)
|
|
102
|
-
.filter(([, value]) => !value)
|
|
103
|
-
.map(([key]) => key);
|
|
111
|
+
const missing = Object.entries(seeded).filter(([, value]) => !value).map(([key]) => key);
|
|
104
112
|
const valueDemoReady = missing.length === 0;
|
|
105
|
-
const operationalization = buildOperationalizationPlan({
|
|
106
|
-
dataDir: os.dataDir,
|
|
107
|
-
firstValueReady: valueDemoReady
|
|
108
|
-
});
|
|
113
|
+
const operationalization = buildOperationalizationPlan({ dataDir: os.dataDir, firstValueReady: valueDemoReady });
|
|
109
114
|
return {
|
|
110
|
-
dataDir: os.dataDir,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
},
|
|
115
|
-
agentMcp: {
|
|
116
|
-
status: 'not_verified',
|
|
117
|
-
verification: 'MCP設定を反映した新しいエージェントセッションで get_context / search を確認してください。'
|
|
118
|
-
},
|
|
119
|
-
operationallyReady: false,
|
|
120
|
-
seeded,
|
|
121
|
-
missing,
|
|
122
|
-
valueDemo: {
|
|
123
|
-
scope: 'local_cli_sample',
|
|
124
|
-
ready: valueDemoReady,
|
|
125
|
-
missing,
|
|
126
|
-
command: `brainbase onboard:demo --dir ${shellArg(os.dataDir)} --scenario "<real request>"`,
|
|
127
|
-
completionSignal: valueDemoReady ? 'cli_sample_ready' : 'needs_seed'
|
|
128
|
-
},
|
|
115
|
+
dataDir: os.dataDir, localBackend: { connected: true, backend: 'local' },
|
|
116
|
+
agentMcp: { status: 'not_verified', verification: 'MCP設定を反映した新しいエージェントセッションで resolve_entity / get_context / search を確認してください。' },
|
|
117
|
+
operationallyReady: false, seeded, missing,
|
|
118
|
+
valueDemo: { scope: 'local_cli_sample', ready: valueDemoReady, missing, command: `brainbase onboard:demo --dir ${shellArg(os.dataDir)} --scenario "<real request>"`, completionSignal: valueDemoReady ? 'cli_sample_ready' : 'needs_seed' },
|
|
129
119
|
operationalization,
|
|
130
|
-
counts: {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
120
|
+
counts: { graphEntities: os.graph.entities.length, graphEdges: os.graph.version === 2 ? os.graph.edges.length : 0, personalKgEntries: os.personalKg.length, relationships: os.relationships.relationships.length, decisions: os.decisions.length, rawSources: os.sourceCount }
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function scopedRecords(os, options) {
|
|
124
|
+
const asOf = options.asOf ?? new Date().toISOString();
|
|
125
|
+
if (os.graph.version !== 2)
|
|
126
|
+
return { records: [], asOf };
|
|
127
|
+
const graph = os.graph;
|
|
128
|
+
const active = graph.entities.filter((entity) => isActiveAt(entity, asOf));
|
|
129
|
+
const activeIds = new Set(active.map((entity) => entity.id));
|
|
130
|
+
const project = options.project ? resolveProject(active, options.project) : undefined;
|
|
131
|
+
if (options.project && !project)
|
|
132
|
+
return { records: [], asOf };
|
|
133
|
+
if (!project)
|
|
134
|
+
return { records: active.map((entity) => ({ entity, relationPath: [] })), asOf };
|
|
135
|
+
const edges = graph.edges.filter((edge) => isActiveAt(edge, asOf) && activeIds.has(edge.fromId) && activeIds.has(edge.toId));
|
|
136
|
+
return {
|
|
137
|
+
records: active.flatMap((entity) => {
|
|
138
|
+
const relationPath = pathToProject(entity.id, project.id, edges);
|
|
139
|
+
return relationPath === undefined ? [] : [{ entity, relationPath }];
|
|
140
|
+
}), project, asOf
|
|
137
141
|
};
|
|
138
142
|
}
|
|
139
|
-
function
|
|
140
|
-
|
|
143
|
+
function pathToProject(entityId, projectId, edges) {
|
|
144
|
+
if (entityId === projectId)
|
|
145
|
+
return [];
|
|
146
|
+
const queue = [{ id: entityId, path: [] }];
|
|
147
|
+
const visited = new Set([entityId]);
|
|
148
|
+
while (queue.length > 0) {
|
|
149
|
+
const current = queue.shift();
|
|
150
|
+
for (const edge of edges) {
|
|
151
|
+
const relation = getCanonicalRelation(edge.relation);
|
|
152
|
+
if (relation.scopeTraversal === 'none' || relation.traversalDirection === 'none')
|
|
153
|
+
continue;
|
|
154
|
+
const next = relation.traversalDirection === 'forward' ? (edge.fromId === current.id ? edge.toId : undefined) : (edge.toId === current.id ? edge.fromId : undefined);
|
|
155
|
+
if (!next)
|
|
156
|
+
continue;
|
|
157
|
+
const path = [...current.path, edge.id];
|
|
158
|
+
if (next === projectId)
|
|
159
|
+
return path;
|
|
160
|
+
if (relation.scopeTraversal === 'project_transitive' && !visited.has(next)) {
|
|
161
|
+
visited.add(next);
|
|
162
|
+
queue.push({ id: next, path });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
function resolveProject(entities, value) {
|
|
169
|
+
const needle = normalize(value);
|
|
170
|
+
const matches = entities.filter((entity) => entity.type === 'project' && [entity.id, entity.name, ...(entity.aliases ?? [])].some((candidate) => normalize(candidate) === needle));
|
|
171
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
172
|
+
}
|
|
173
|
+
function projectionTargets(graph, asOf, visibleIds) {
|
|
174
|
+
const person = new Map();
|
|
175
|
+
const decision = new Map();
|
|
176
|
+
const ambiguous = new Set();
|
|
177
|
+
for (const entity of graph.entities.filter((candidate) => visibleIds.has(candidate.id) && isActiveAt(candidate, asOf))) {
|
|
178
|
+
const target = entity.type === 'person' ? person : entity.type === 'decision' ? decision : undefined;
|
|
179
|
+
if (!target)
|
|
180
|
+
continue;
|
|
181
|
+
for (const value of [entity.id, entity.name, ...(entity.aliases ?? [])]) {
|
|
182
|
+
const key = value === entity.id ? value : normalize(value);
|
|
183
|
+
if (target.has(key) && target.get(key) !== entity.id)
|
|
184
|
+
ambiguous.add(`${entity.type}:${key}`);
|
|
185
|
+
else
|
|
186
|
+
target.set(key, entity.id);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
for (const marker of ambiguous) {
|
|
190
|
+
const [kind, ...parts] = marker.split(':');
|
|
191
|
+
(kind === 'person' ? person : decision).delete(parts.join(':'));
|
|
192
|
+
}
|
|
193
|
+
return { person, decision };
|
|
141
194
|
}
|
|
142
195
|
function rank(results, limit) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
.sort((a, b) => b.score - a.score || a.title.localeCompare(b.title))
|
|
196
|
+
const priority = { canonical: 0, projection: 1, unresolved: 2 };
|
|
197
|
+
return results.filter((result) => result.score > 0)
|
|
198
|
+
.sort((a, b) => b.score - a.score || priority[a.recordClass] - priority[b.recordClass] || a.title.localeCompare(b.title))
|
|
146
199
|
.slice(0, Math.max(1, limit));
|
|
147
200
|
}
|
|
148
201
|
function scoreText(query, fields) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
202
|
+
let best = 0;
|
|
203
|
+
for (const variant of queryAliases(query)) {
|
|
204
|
+
const terms = searchTokens(variant);
|
|
205
|
+
if (terms.length === 0)
|
|
206
|
+
continue;
|
|
207
|
+
const haystack = fields.join('\n').normalize('NFKC').toLowerCase();
|
|
208
|
+
const phrase = variant.trim().normalize('NFKC').toLowerCase();
|
|
209
|
+
const matched = terms.filter((term) => haystack.includes(term));
|
|
210
|
+
best = Math.max(best, phrase && haystack.includes(phrase) ? terms.length + 20 : matched.length === terms.length ? terms.length + 10 : matched.length);
|
|
158
211
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
212
|
+
return best;
|
|
213
|
+
}
|
|
214
|
+
function queryAliases(query) {
|
|
215
|
+
const canonical = query.normalize('NFKC').trim();
|
|
216
|
+
const alias = canonical.replace(/(?:さん|様|氏|くん|君|ちゃん)$/u, '').trim();
|
|
217
|
+
return alias && alias !== canonical ? [canonical, alias] : [canonical];
|
|
163
218
|
}
|
|
164
219
|
function searchTokens(query) {
|
|
165
220
|
const seen = new Set();
|
|
166
|
-
return query
|
|
167
|
-
.
|
|
168
|
-
.
|
|
169
|
-
.replace(/[-_]+/g, ' ')
|
|
170
|
-
.toLowerCase()
|
|
171
|
-
.split(/\s+/)
|
|
172
|
-
.map((term) => term.trim())
|
|
173
|
-
.filter(Boolean)
|
|
174
|
-
.filter((term) => {
|
|
175
|
-
if (seen.has(term)) {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
seen.add(term);
|
|
179
|
-
return true;
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
function metadataText(metadata) {
|
|
183
|
-
if (!metadata) {
|
|
184
|
-
return '';
|
|
185
|
-
}
|
|
186
|
-
return JSON.stringify(metadata);
|
|
187
|
-
}
|
|
188
|
-
function readMetadataString(metadata, key) {
|
|
189
|
-
const value = metadata?.[key];
|
|
190
|
-
return typeof value === 'string' ? value : undefined;
|
|
221
|
+
return query.normalize('NFKC').replace(/[|/\\,;:()[\]{}"'`“”‘’、。・・,.:;()[]{}「」『』【】]/g, ' ')
|
|
222
|
+
.replace(/[-_]+/g, ' ').toLowerCase().split(/\s+/).map((term) => term.trim()).filter(Boolean)
|
|
223
|
+
.filter((term) => seen.has(term) ? false : (seen.add(term), true));
|
|
191
224
|
}
|
|
225
|
+
function normalize(value) { return value.normalize('NFKC').replace(/\s+/g, '').toLowerCase(); }
|
|
226
|
+
function metadataText(metadata) { return metadata ? JSON.stringify(metadata) : ''; }
|
|
227
|
+
function readMetadataString(metadata, key) { const value = metadata?.[key]; return typeof value === 'string' ? value : undefined; }
|
|
228
|
+
function shellArg(value) { return /^[A-Za-z0-9_./:@%+=,-]+$/.test(value) ? value : JSON.stringify(value); }
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export type EntityKind = 'person' | 'org' | 'project' | 'relationship' | 'decision';
|
|
2
|
+
export type CanonicalEntityKind = 'person' | 'org' | 'project' | 'decision';
|
|
3
|
+
export type CoreRelation = 'member_of' | 'participates_in' | 'accountable_for' | 'owned_by' | 'governs' | 'supersedes';
|
|
2
4
|
export interface GraphEntity {
|
|
3
5
|
id: string;
|
|
4
6
|
type: Exclude<EntityKind, 'decision'>;
|
|
@@ -7,7 +9,7 @@ export interface GraphEntity {
|
|
|
7
9
|
tags?: string[];
|
|
8
10
|
metadata?: Record<string, unknown>;
|
|
9
11
|
}
|
|
10
|
-
export interface
|
|
12
|
+
export interface GraphFileV1 {
|
|
11
13
|
version: 1;
|
|
12
14
|
owner?: {
|
|
13
15
|
name?: string;
|
|
@@ -15,6 +17,49 @@ export interface GraphFile {
|
|
|
15
17
|
};
|
|
16
18
|
entities: GraphEntity[];
|
|
17
19
|
}
|
|
20
|
+
export interface CanonicalEntity {
|
|
21
|
+
id: string;
|
|
22
|
+
type: CanonicalEntityKind;
|
|
23
|
+
name: string;
|
|
24
|
+
aliases?: string[];
|
|
25
|
+
summary?: string;
|
|
26
|
+
tags?: string[];
|
|
27
|
+
validFrom?: string;
|
|
28
|
+
validTo?: string;
|
|
29
|
+
metadata?: Record<string, unknown>;
|
|
30
|
+
}
|
|
31
|
+
export interface CanonicalEdge {
|
|
32
|
+
id: string;
|
|
33
|
+
fromId: string;
|
|
34
|
+
relation: CoreRelation;
|
|
35
|
+
toId: string;
|
|
36
|
+
role?: string;
|
|
37
|
+
context?: string;
|
|
38
|
+
validFrom?: string;
|
|
39
|
+
validTo?: string;
|
|
40
|
+
provenance?: {
|
|
41
|
+
sourceKind: 'user_approved' | 'migration' | 'import' | 'onboarding';
|
|
42
|
+
sourceId?: string;
|
|
43
|
+
evidenceHash?: string;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export interface GraphFileV2 {
|
|
47
|
+
version: 2;
|
|
48
|
+
ontology: {
|
|
49
|
+
id: 'brainbase-personal-os';
|
|
50
|
+
version: string;
|
|
51
|
+
releaseDigest: string;
|
|
52
|
+
};
|
|
53
|
+
owner?: {
|
|
54
|
+
id?: string;
|
|
55
|
+
name?: string;
|
|
56
|
+
summary?: string;
|
|
57
|
+
};
|
|
58
|
+
entities: CanonicalEntity[];
|
|
59
|
+
edges: CanonicalEdge[];
|
|
60
|
+
}
|
|
61
|
+
export type GraphFile = GraphFileV1 | GraphFileV2;
|
|
62
|
+
export type CanonicalGraphFile = GraphFile;
|
|
18
63
|
export interface PersonalKgEntry {
|
|
19
64
|
id: string;
|
|
20
65
|
type: 'self' | 'work' | 'relationship' | 'value' | 'judgment' | 'experience' | 'sns_context';
|
|
@@ -60,4 +105,10 @@ export interface SearchResult {
|
|
|
60
105
|
title: string;
|
|
61
106
|
text: string;
|
|
62
107
|
score: number;
|
|
108
|
+
canonicalEntityId?: string;
|
|
109
|
+
recordClass: 'canonical' | 'projection' | 'unresolved';
|
|
110
|
+
projectionOf?: string;
|
|
111
|
+
projectionSources?: Array<'relationships' | 'decisions'>;
|
|
112
|
+
relationPath?: string[];
|
|
113
|
+
authority: 'local_graph' | 'personal_kg' | 'legacy_relationships' | 'legacy_decisions';
|
|
63
114
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Local-first Brainbase MCP server and personal onboarding kit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"scripts": {
|
|
27
27
|
"build": "tsc -p tsconfig.json",
|
|
28
28
|
"test": "vitest run",
|
|
29
|
-
"test:integration": "vitest run tests/mcp-contract.test.ts tests/ontology-cli.test.ts tests/npm-release-validation.integration.test.ts",
|
|
29
|
+
"test:integration": "vitest run tests/mcp-contract.test.ts tests/ontology-cli.test.ts tests/npm-release-validation.integration.test.ts tests/npm-consumer-smoke.integration.test.ts",
|
|
30
|
+
"test:consumer-smoke": "vitest run tests/npm-consumer-smoke.integration.test.ts",
|
|
30
31
|
"test:integration:release-evidence": "vitest run tests/npm-release-validation.integration.test.ts tests/npm-prepublication-evidence.integration.test.ts",
|
|
31
32
|
"test:e2e": "vitest run tests/e2e/brainbase-mcp-only-acceptance.spec.ts tests/e2e/story-brainbase-portable-ontology-kernel-acceptance.spec.ts",
|
|
32
33
|
"docs:dev": "vitepress dev docs",
|
|
@@ -61,5 +62,5 @@
|
|
|
61
62
|
"engines": {
|
|
62
63
|
"node": ">=20"
|
|
63
64
|
},
|
|
64
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "0ff2753a492aef09235be2792570000ef03dfa4b"
|
|
65
66
|
}
|