@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/ssot.js
CHANGED
|
@@ -4,28 +4,14 @@ import { access, copyFile, mkdir, readFile, readdir, rename, rm, stat, writeFile
|
|
|
4
4
|
import { hostname } from 'node:os';
|
|
5
5
|
import { dirname, isAbsolute, join, relative, resolve, win32 } from 'node:path';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
+
import { validateCanonicalGraph } from './canonical-graph.js';
|
|
7
8
|
import { assertOntologyValid } from './ontology.js';
|
|
9
|
+
import { planCanonicalGraphMigration } from './ontology-migration.js';
|
|
8
10
|
import { emptyGraph, emptyRelationships, schemaTemplates } from './templates.js';
|
|
9
11
|
const canonicalFiles = ['graph.json', 'relationships.json', 'personal-kg.jsonl', 'decisions.jsonl'];
|
|
10
12
|
const lockName = '.brainbase-ssot.lock';
|
|
11
13
|
const stagingPrefix = '.brainbase-staging-';
|
|
12
14
|
const transactionPrefix = '.brainbase-transaction-';
|
|
13
|
-
const graphEntitySchema = z.object({
|
|
14
|
-
id: z.string().min(1),
|
|
15
|
-
type: z.enum(['person', 'org', 'project', 'relationship']),
|
|
16
|
-
name: z.string().min(1),
|
|
17
|
-
summary: z.string().optional(),
|
|
18
|
-
tags: z.array(z.string()).optional(),
|
|
19
|
-
metadata: z.record(z.unknown()).optional()
|
|
20
|
-
});
|
|
21
|
-
const graphSchema = z.object({
|
|
22
|
-
version: z.literal(1),
|
|
23
|
-
owner: z.object({
|
|
24
|
-
name: z.string().optional(),
|
|
25
|
-
summary: z.string().optional()
|
|
26
|
-
}).optional(),
|
|
27
|
-
entities: z.array(graphEntitySchema)
|
|
28
|
-
});
|
|
29
15
|
const personalKgSchema = z.object({
|
|
30
16
|
id: z.string().min(1),
|
|
31
17
|
type: z.enum(['self', 'work', 'relationship', 'value', 'judgment', 'experience', 'sns_context']),
|
|
@@ -112,8 +98,56 @@ export async function mutatePersonalOsWithSidecar(dataDir, sidecarPath, mutator)
|
|
|
112
98
|
return mutation.result;
|
|
113
99
|
});
|
|
114
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Plans or atomically applies the canonical Graph migration.
|
|
103
|
+
*
|
|
104
|
+
* The input is always recovered, read, and planned while holding the same
|
|
105
|
+
* lock used by every canonical SSOT writer. Omitting `write` is a byte-safe
|
|
106
|
+
* preview; a blocked or already-current plan is never committed.
|
|
107
|
+
*/
|
|
108
|
+
export async function migrateCanonicalGraph(dataDir, options = {}) {
|
|
109
|
+
return withSsotLock(dataDir, async () => {
|
|
110
|
+
await recoverTransactions(dataDir);
|
|
111
|
+
assertCompleteCanonicalSet(dataDir, await canonicalPresence(dataDir));
|
|
112
|
+
const current = await loadPersonalOsUnlocked(dataDir);
|
|
113
|
+
const plan = planCanonicalGraphMigration({
|
|
114
|
+
graph: current.graph,
|
|
115
|
+
relationships: current.relationships,
|
|
116
|
+
decisions: current.decisions
|
|
117
|
+
});
|
|
118
|
+
if (options.write && options.expectedInputDigest === undefined) {
|
|
119
|
+
return blockMigrationWrite(plan, {
|
|
120
|
+
code: 'expected_input_digest_required',
|
|
121
|
+
recordId: 'canonical-aggregate',
|
|
122
|
+
detail: 'MIGRATION-EXPECTED-INPUT-DIGEST-REQUIRED: preview first and pass its inputDigest before writing'
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (options.write && options.expectedInputDigest !== plan.inputDigest) {
|
|
126
|
+
return blockMigrationWrite(plan, {
|
|
127
|
+
code: 'input_digest_mismatch',
|
|
128
|
+
recordId: 'canonical-aggregate',
|
|
129
|
+
detail: `MIGRATION-INPUT-DIGEST-MISMATCH: expected ${options.expectedInputDigest}, replanned ${plan.inputDigest}`
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
if (!options.write || plan.status !== 'migration_required') {
|
|
133
|
+
return { ...plan, expectedInputDigest: plan.inputDigest, written: false };
|
|
134
|
+
}
|
|
135
|
+
const next = { ...current, graph: plan.graph };
|
|
136
|
+
await commitAggregate(dataDir, next, 'mutation');
|
|
137
|
+
return { ...plan, expectedInputDigest: plan.inputDigest, written: true };
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
function blockMigrationWrite(plan, issue) {
|
|
141
|
+
return {
|
|
142
|
+
...plan,
|
|
143
|
+
status: 'blocked',
|
|
144
|
+
issues: [...plan.issues, issue].sort((left, right) => (`${left.recordId}\u0000${left.code}`.localeCompare(`${right.recordId}\u0000${right.code}`, 'en'))),
|
|
145
|
+
expectedInputDigest: plan.inputDigest,
|
|
146
|
+
written: false
|
|
147
|
+
};
|
|
148
|
+
}
|
|
115
149
|
async function loadPersonalOsUnlocked(dataDir) {
|
|
116
|
-
const graph =
|
|
150
|
+
const graph = parseGraph(await readJson(join(dataDir, 'graph.json')));
|
|
117
151
|
const relationships = relationshipsSchema.parse(await readJson(join(dataDir, 'relationships.json')));
|
|
118
152
|
const personalKg = await readJsonl(join(dataDir, 'personal-kg.jsonl'), personalKgSchema, 'personal-kg.jsonl');
|
|
119
153
|
const decisions = await readJsonl(join(dataDir, 'decisions.jsonl'), decisionSchema, 'decisions.jsonl');
|
|
@@ -229,11 +263,27 @@ async function writeAggregate(targetDir, os) {
|
|
|
229
263
|
await writeFile(join(targetDir, 'decisions.jsonl'), serializeJsonl(os.decisions));
|
|
230
264
|
}
|
|
231
265
|
function validateAggregate(os) {
|
|
232
|
-
graphSchema.parse(os.graph);
|
|
233
266
|
relationshipsSchema.parse(os.relationships);
|
|
234
267
|
os.personalKg.forEach((entry) => personalKgSchema.parse(entry));
|
|
235
268
|
os.decisions.forEach((decision) => decisionSchema.parse(decision));
|
|
236
269
|
assertOntologyValid(os);
|
|
270
|
+
validateCanonicalGraph(os.graph);
|
|
271
|
+
}
|
|
272
|
+
function parseGraph(value) {
|
|
273
|
+
try {
|
|
274
|
+
validateCanonicalGraph(value);
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
// Duplicate entity IDs are a complete, readable snapshot whose ontology
|
|
278
|
+
// violation must remain available to audit/inference instead of being
|
|
279
|
+
// collapsed into a source-unavailable result. Writers still validate the
|
|
280
|
+
// aggregate strictly before commit.
|
|
281
|
+
if (!(error instanceof Error) || !error.message.startsWith('GRAPH-ENTITY-ID-UNIQUE')) {
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
validateCanonicalGraph(value, { allowDuplicateEntityIds: true });
|
|
285
|
+
}
|
|
286
|
+
return value;
|
|
237
287
|
}
|
|
238
288
|
function serializeJsonl(values) {
|
|
239
289
|
return values.length === 0 ? '' : `${values.map((value) => JSON.stringify(value)).join('\n')}\n`;
|
package/dist/templates.d.ts
CHANGED
|
@@ -1,4 +1,112 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare const
|
|
1
|
+
import type { GraphFileV2, RelationshipsFile } from './types.js';
|
|
2
|
+
export declare const canonicalGraphOntologyRelease: {
|
|
3
|
+
manifest: {
|
|
4
|
+
id: "brainbase-personal-os";
|
|
5
|
+
version: "2.0.0";
|
|
6
|
+
ontology: {
|
|
7
|
+
readonly version: "2.0.0";
|
|
8
|
+
readonly effectiveAt: "2026-08-17T00:00:00.000Z";
|
|
9
|
+
readonly compatibility: "read-compatible-write-gated";
|
|
10
|
+
readonly name: "Brainbase Portable Ontology Kernel";
|
|
11
|
+
readonly description: "A local-first semantic contract for canonical Graph entities and ID-based edges.";
|
|
12
|
+
readonly domains: {
|
|
13
|
+
readonly types: {
|
|
14
|
+
readonly concepts: readonly [{
|
|
15
|
+
readonly id: "person";
|
|
16
|
+
readonly meaning: "A human represented in the local Graph.";
|
|
17
|
+
readonly usageConditions: readonly ["Use only for a human identity approved for the canonical local SSOT."];
|
|
18
|
+
}, {
|
|
19
|
+
readonly id: "org";
|
|
20
|
+
readonly meaning: "An organization represented in the local Graph.";
|
|
21
|
+
readonly usageConditions: readonly ["Use for a named organizational actor, not for a project or product."];
|
|
22
|
+
}, {
|
|
23
|
+
readonly id: "project";
|
|
24
|
+
readonly meaning: "A bounded body of work represented in the local Graph.";
|
|
25
|
+
readonly usageConditions: readonly ["Use when the entity has a bounded work objective; do not use it as an organization alias."];
|
|
26
|
+
}, {
|
|
27
|
+
readonly id: "relationship";
|
|
28
|
+
readonly meaning: "A contextual connection to a person.";
|
|
29
|
+
readonly usageConditions: readonly ["The person field must resolve to a canonical person entity by name."];
|
|
30
|
+
}, {
|
|
31
|
+
readonly id: "decision";
|
|
32
|
+
readonly meaning: "A durable choice that may explicitly supersede another choice.";
|
|
33
|
+
readonly usageConditions: readonly ["Use for an explicit durable choice; replacement requires a supersedes Decision ID."];
|
|
34
|
+
}];
|
|
35
|
+
};
|
|
36
|
+
readonly relations: {
|
|
37
|
+
readonly vocabulary: {
|
|
38
|
+
id: import("./types.js").CoreRelation;
|
|
39
|
+
source: import("./types.js").CanonicalEntityKind;
|
|
40
|
+
target: import("./types.js").CanonicalEntityKind;
|
|
41
|
+
}[];
|
|
42
|
+
};
|
|
43
|
+
readonly constraints: {
|
|
44
|
+
readonly rules: readonly [{
|
|
45
|
+
readonly id: "ONT-ENTITY-ID-UNIQUE";
|
|
46
|
+
readonly severity: "error";
|
|
47
|
+
readonly meaning: "Graph entity IDs must be unique.";
|
|
48
|
+
}, {
|
|
49
|
+
readonly id: "ONT-RELATIONSHIP-ID-UNIQUE";
|
|
50
|
+
readonly severity: "error";
|
|
51
|
+
readonly meaning: "Relationship IDs must be unique.";
|
|
52
|
+
}, {
|
|
53
|
+
readonly id: "ONT-DECISION-ID-UNIQUE";
|
|
54
|
+
readonly severity: "error";
|
|
55
|
+
readonly meaning: "Decision IDs must be unique.";
|
|
56
|
+
}, {
|
|
57
|
+
readonly id: "ONT-RELATIONSHIP-PERSON-RESOLVES";
|
|
58
|
+
readonly severity: "warning";
|
|
59
|
+
readonly meaning: "A relationship person should resolve to a canonical person entity by name.";
|
|
60
|
+
}, {
|
|
61
|
+
readonly id: "ONT-DECISION-SUPERSEDES-EXISTS";
|
|
62
|
+
readonly severity: "error";
|
|
63
|
+
readonly meaning: "A supersedes reference must resolve to an existing decision.";
|
|
64
|
+
}, {
|
|
65
|
+
readonly id: "ONT-DECISION-SUPERSEDES-SELF";
|
|
66
|
+
readonly severity: "error";
|
|
67
|
+
readonly meaning: "A decision must not supersede itself.";
|
|
68
|
+
}, {
|
|
69
|
+
readonly id: "ONT-DECISION-SUPERSEDES-CYCLE";
|
|
70
|
+
readonly severity: "error";
|
|
71
|
+
readonly meaning: "Decision supersession edges must not form a cycle.";
|
|
72
|
+
}];
|
|
73
|
+
};
|
|
74
|
+
readonly inference: {
|
|
75
|
+
readonly rules: readonly [{
|
|
76
|
+
readonly id: "ONT-INFER-EXPLICIT-SUPERSESSION";
|
|
77
|
+
readonly meaning: "Only an explicit supersedes edge makes an older decision inactive.";
|
|
78
|
+
}, {
|
|
79
|
+
readonly id: "ONT-INFER-SAME-TOPIC-CONFLICT";
|
|
80
|
+
readonly meaning: "Multiple active decisions on the same explicit topic are reported as a conflict.";
|
|
81
|
+
}];
|
|
82
|
+
};
|
|
83
|
+
readonly evolution: {
|
|
84
|
+
readonly compatibility: readonly [{
|
|
85
|
+
readonly fromVersion: "0.0.0";
|
|
86
|
+
readonly toVersion: "2.0.0";
|
|
87
|
+
readonly level: "read-compatible-write-gated";
|
|
88
|
+
readonly changes: readonly ["Adds a versioned public semantic contract.", "Adds canonical Graph v2 entities and ID-based edges governed by the Relation Registry."];
|
|
89
|
+
readonly migration: "Before enabling 2.0.0 writes, back up the Personal OS directory, run ontology:audit, preview ontology:migrate, then write using the preview expectedInputDigest.";
|
|
90
|
+
readonly rollback: "For an installation without a prior package, run npm uninstall -g @unson/brainbase-mcp, restore the captured MCP client configuration, and restart the client. Otherwise restore the pre-migration Personal OS backup and reinstall the recorded last known working package version.";
|
|
91
|
+
}, {
|
|
92
|
+
readonly fromVersion: "1.0.0";
|
|
93
|
+
readonly toVersion: "2.0.0";
|
|
94
|
+
readonly level: "read-compatible-write-gated";
|
|
95
|
+
readonly changes: readonly ["Adds canonical Graph v2 entities and ID-based edges.", "Binds the portable ontology release to the canonical Relation Registry."];
|
|
96
|
+
readonly migration: "Run ontology:audit --ontology-version 1.0.0, preview ontology:migrate, then write using the preview expectedInputDigest.";
|
|
97
|
+
readonly rollback: "Restore the pre-migration Personal OS backup; the immutable 1.0.0 interpretation remains available for historical reads.";
|
|
98
|
+
}];
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
relationRegistry: Readonly<Record<import("./types.js").CoreRelation, Readonly<import("./relation-registry.js").CanonicalRelationDefinition>>>;
|
|
103
|
+
};
|
|
104
|
+
binding: {
|
|
105
|
+
id: "brainbase-personal-os";
|
|
106
|
+
version: "2.0.0";
|
|
107
|
+
releaseDigest: string;
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
export declare const emptyGraph: GraphFileV2;
|
|
3
111
|
export declare const emptyRelationships: RelationshipsFile;
|
|
4
112
|
export declare const schemaTemplates: Record<string, unknown>;
|
package/dist/templates.js
CHANGED
|
@@ -1,21 +1,61 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { portableOntology } from './ontology.js';
|
|
3
|
+
import { canonicalRelationRegistry } from './relation-registry.js';
|
|
4
|
+
const ontologyReleaseManifest = deepFreeze({
|
|
5
|
+
id: 'brainbase-personal-os',
|
|
6
|
+
version: portableOntology.version,
|
|
7
|
+
ontology: portableOntology,
|
|
8
|
+
relationRegistry: canonicalRelationRegistry
|
|
9
|
+
});
|
|
10
|
+
const ontologyReleaseDigest = `sha256:${createHash('sha256')
|
|
11
|
+
.update(JSON.stringify(ontologyReleaseManifest))
|
|
12
|
+
.digest('hex')}`;
|
|
13
|
+
export const canonicalGraphOntologyRelease = deepFreeze({
|
|
14
|
+
manifest: ontologyReleaseManifest,
|
|
15
|
+
binding: {
|
|
16
|
+
id: ontologyReleaseManifest.id,
|
|
17
|
+
version: ontologyReleaseManifest.version,
|
|
18
|
+
releaseDigest: ontologyReleaseDigest
|
|
19
|
+
}
|
|
20
|
+
});
|
|
1
21
|
export const emptyGraph = {
|
|
2
|
-
version:
|
|
22
|
+
version: 2,
|
|
23
|
+
ontology: canonicalGraphOntologyRelease.binding,
|
|
3
24
|
owner: {},
|
|
4
|
-
entities: []
|
|
25
|
+
entities: [],
|
|
26
|
+
edges: []
|
|
5
27
|
};
|
|
6
28
|
export const emptyRelationships = {
|
|
7
29
|
version: 1,
|
|
8
30
|
relationships: []
|
|
9
31
|
};
|
|
32
|
+
function deepFreeze(value) {
|
|
33
|
+
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
34
|
+
Object.freeze(value);
|
|
35
|
+
for (const nested of Object.values(value))
|
|
36
|
+
deepFreeze(nested);
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
10
40
|
export const schemaTemplates = {
|
|
11
41
|
'graph.schema.json': {
|
|
12
42
|
type: 'object',
|
|
13
|
-
required: ['version', 'entities'],
|
|
43
|
+
required: ['version', 'ontology', 'entities', 'edges'],
|
|
14
44
|
properties: {
|
|
15
|
-
version: { const:
|
|
45
|
+
version: { const: 2 },
|
|
46
|
+
ontology: {
|
|
47
|
+
type: 'object',
|
|
48
|
+
required: ['id', 'version', 'releaseDigest'],
|
|
49
|
+
properties: {
|
|
50
|
+
id: { const: 'brainbase-personal-os' },
|
|
51
|
+
version: { type: 'string', minLength: 1 },
|
|
52
|
+
releaseDigest: { type: 'string', minLength: 1 }
|
|
53
|
+
}
|
|
54
|
+
},
|
|
16
55
|
owner: {
|
|
17
56
|
type: 'object',
|
|
18
57
|
properties: {
|
|
58
|
+
id: { type: 'string' },
|
|
19
59
|
name: { type: 'string' },
|
|
20
60
|
summary: { type: 'string' }
|
|
21
61
|
}
|
|
@@ -27,13 +67,42 @@ export const schemaTemplates = {
|
|
|
27
67
|
required: ['id', 'type', 'name'],
|
|
28
68
|
properties: {
|
|
29
69
|
id: { type: 'string', minLength: 1 },
|
|
30
|
-
type: { enum: ['person', 'org', 'project', '
|
|
70
|
+
type: { enum: ['person', 'org', 'project', 'decision'] },
|
|
31
71
|
name: { type: 'string', minLength: 1 },
|
|
72
|
+
aliases: { type: 'array', items: { type: 'string', minLength: 1 } },
|
|
32
73
|
summary: { type: 'string' },
|
|
33
74
|
tags: { type: 'array', items: { type: 'string' } },
|
|
34
75
|
metadata: { type: 'object' }
|
|
35
76
|
}
|
|
36
77
|
}
|
|
78
|
+
},
|
|
79
|
+
edges: {
|
|
80
|
+
type: 'array',
|
|
81
|
+
items: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
required: ['id', 'fromId', 'relation', 'toId'],
|
|
84
|
+
properties: {
|
|
85
|
+
id: { type: 'string', minLength: 1 },
|
|
86
|
+
fromId: { type: 'string', minLength: 1 },
|
|
87
|
+
relation: {
|
|
88
|
+
enum: ['member_of', 'participates_in', 'accountable_for', 'owned_by', 'governs', 'supersedes']
|
|
89
|
+
},
|
|
90
|
+
toId: { type: 'string', minLength: 1 },
|
|
91
|
+
role: { type: 'string' },
|
|
92
|
+
context: { type: 'string' },
|
|
93
|
+
validFrom: { type: 'string', format: 'date-time' },
|
|
94
|
+
validTo: { type: 'string', format: 'date-time' },
|
|
95
|
+
provenance: {
|
|
96
|
+
type: 'object',
|
|
97
|
+
required: ['sourceKind'],
|
|
98
|
+
properties: {
|
|
99
|
+
sourceKind: { enum: ['user_approved', 'migration', 'import', 'onboarding'] },
|
|
100
|
+
sourceId: { type: 'string' },
|
|
101
|
+
evidenceHash: { type: 'string' }
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
37
106
|
}
|
|
38
107
|
}
|
|
39
108
|
},
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { EntityKind, PersonalOs, SearchResult } from './types.js';
|
|
2
|
-
export
|
|
2
|
+
export interface RetrievalOptions {
|
|
3
|
+
project?: string;
|
|
4
|
+
asOf?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function getContext(os: PersonalOs, options?: RetrievalOptions): Record<string, unknown>;
|
|
3
7
|
export declare function listEntities(os: PersonalOs, type?: EntityKind): Record<string, unknown>;
|
|
4
8
|
export declare function searchPersonalKg(os: PersonalOs, query: string, limit?: number): SearchResult[];
|
|
5
|
-
export declare function searchAll(os: PersonalOs, query: string, limit?: number): SearchResult[];
|
|
9
|
+
export declare function searchAll(os: PersonalOs, query: string, limit?: number, options?: RetrievalOptions): SearchResult[];
|
|
6
10
|
export declare function onboardingStatus(os: PersonalOs): Record<string, unknown>;
|