@unson/brainbase-mcp 0.4.1 → 0.5.0

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.
@@ -0,0 +1,240 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { validateCanonicalGraph } from './canonical-graph.js';
3
+ import { retrieveGraph } from './graph-retrieval.js';
4
+ /** The public, read-only interchange format for a canonical Graph v2 snapshot. */
5
+ export const PORTABLE_GRAPH_SCHEMA_VERSION = 1;
6
+ /** Bounds keep an imported snapshot finite before it reaches a runtime or model. */
7
+ export const PORTABLE_GRAPH_MAX_ENTITIES = 10_000;
8
+ export const PORTABLE_GRAPH_MAX_EDGES = 25_000;
9
+ export const PORTABLE_GRAPH_MAX_DECISIONS = 10_000;
10
+ export const PORTABLE_GRAPH_MAX_JSON_BYTES = 8_000_000;
11
+ export const PORTABLE_GRAPH_MAX_JSON_DEPTH = 64;
12
+ /**
13
+ * Create a detached Graph v2 bundle for transfer between the OSS and
14
+ * organization runtimes. Personal KG, legacy relationships, source files, and
15
+ * the local data directory are deliberately outside this contract.
16
+ */
17
+ export function createPortableGraph(os) {
18
+ if (!os || typeof os !== 'object' || !('graph' in os) || !('decisions' in os)) {
19
+ throw new Error('PORTABLE-GRAPH-SOURCE: a PersonalOs with graph and decisions is required');
20
+ }
21
+ validateCanonicalGraph(os.graph);
22
+ if (os.graph.version !== 2) {
23
+ throw new Error('PORTABLE-GRAPH-V1-REJECTED: only Graph v2 can be exported');
24
+ }
25
+ if (!Array.isArray(os.decisions)) {
26
+ throw new Error('PORTABLE-GRAPH-DECISIONS: decisions must be an array');
27
+ }
28
+ const decisionIds = new Set(os.graph.entities.filter((entity) => entity.type === 'decision').map((entity) => entity.id));
29
+ const decisions = os.decisions
30
+ .filter((decision) => decisionIds.has(decision.id))
31
+ .map((decision) => structuredClone(decision))
32
+ .sort((left, right) => left.id.localeCompare(right.id, 'en'));
33
+ const bundle = {
34
+ schemaVersion: PORTABLE_GRAPH_SCHEMA_VERSION,
35
+ graph: structuredClone(os.graph),
36
+ decisions
37
+ };
38
+ validatePortableGraph(bundle);
39
+ return bundle;
40
+ }
41
+ /**
42
+ * Validate the exact portable contract. Unknown top-level fields are rejected
43
+ * so an organization runtime cannot silently ignore a new capability. Unknown
44
+ * fields inside Graph records are retained after the canonical Graph validator
45
+ * accepts them, which keeps forward-compatible metadata lossless.
46
+ */
47
+ export function validatePortableGraph(value) {
48
+ assertJsonValue(value, '$', new WeakSet(), 0);
49
+ if (!isPlainRecord(value))
50
+ throw new Error('PORTABLE-GRAPH-SHAPE: bundle must be a plain JSON object');
51
+ const topLevelKeys = Object.getOwnPropertyNames(value).sort();
52
+ const expected = ['decisions', 'graph', 'schemaVersion'];
53
+ const unknownKeys = topLevelKeys.filter((key) => !expected.includes(key));
54
+ if (unknownKeys.length > 0) {
55
+ throw new Error(`PORTABLE-GRAPH-SHAPE: unknown top-level field ${unknownKeys[0]}`);
56
+ }
57
+ if (value.schemaVersion !== PORTABLE_GRAPH_SCHEMA_VERSION) {
58
+ throw new Error(`PORTABLE-GRAPH-VERSION: unsupported portable schema version ${String(value.schemaVersion)}`);
59
+ }
60
+ if (!isPlainRecord(value.graph)) {
61
+ throw new Error('PORTABLE-GRAPH-SHAPE: graph must be a plain JSON object');
62
+ }
63
+ if (value.graph.version === 1) {
64
+ throw new Error('PORTABLE-GRAPH-V1-REJECTED: portable bundles require Graph v2');
65
+ }
66
+ if (!Array.isArray(value.decisions)) {
67
+ throw new Error('PORTABLE-GRAPH-DECISIONS: decisions must be an array');
68
+ }
69
+ const graph = value.graph;
70
+ if (graph.version !== 2) {
71
+ // Keep the canonical validator's explicit version error for other future
72
+ // versions while still making the portable boundary clear to callers.
73
+ validateCanonicalGraph(graph);
74
+ }
75
+ validateCanonicalGraph(graph);
76
+ if (graph.entities.length > PORTABLE_GRAPH_MAX_ENTITIES) {
77
+ throw new Error(`PORTABLE-GRAPH-BOUND: graph.entities exceeds ${PORTABLE_GRAPH_MAX_ENTITIES}`);
78
+ }
79
+ if (graph.edges.length > PORTABLE_GRAPH_MAX_EDGES) {
80
+ throw new Error(`PORTABLE-GRAPH-BOUND: graph.edges exceeds ${PORTABLE_GRAPH_MAX_EDGES}`);
81
+ }
82
+ if (value.decisions.length > PORTABLE_GRAPH_MAX_DECISIONS) {
83
+ throw new Error(`PORTABLE-GRAPH-BOUND: decisions exceeds ${PORTABLE_GRAPH_MAX_DECISIONS}`);
84
+ }
85
+ validateDecisionRecords(graph, value.decisions);
86
+ const bytes = Buffer.byteLength(canonicalPortableJson(value), 'utf8');
87
+ if (bytes > PORTABLE_GRAPH_MAX_JSON_BYTES) {
88
+ throw new Error(`PORTABLE-GRAPH-BOUND: JSON payload exceeds ${PORTABLE_GRAPH_MAX_JSON_BYTES} bytes`);
89
+ }
90
+ }
91
+ /** Rehydrate a validated bundle into the shared retrieval input shape. */
92
+ export function hydratePortableGraph(bundle) {
93
+ validatePortableGraph(bundle);
94
+ return {
95
+ dataDir: '',
96
+ graph: structuredClone(bundle.graph),
97
+ personalKg: [],
98
+ relationships: { version: 1, relationships: [] },
99
+ decisions: structuredClone(bundle.decisions),
100
+ // sourceCount counts imported source files in the local SSOT. A portable
101
+ // Graph bundle contains no source files, so zero is the truthful value.
102
+ sourceCount: 0
103
+ };
104
+ }
105
+ /** Use exactly the OSS retrieval implementation against a portable bundle. */
106
+ export function retrievePortableGraph(bundle, input, provider) {
107
+ return retrieveGraph(hydratePortableGraph(bundle), input, provider);
108
+ }
109
+ /** Canonical JSON with recursively sorted object keys and preserved array order. */
110
+ export function canonicalPortableJson(value) {
111
+ assertJsonValue(value, '$', new WeakSet(), 0);
112
+ return stableJson(value, 0);
113
+ }
114
+ /** Stable content identity for a portable Graph bundle. */
115
+ export function portableGraphDigest(value) {
116
+ validatePortableGraph(value);
117
+ return `sha256:${createHash('sha256').update(canonicalPortableJson(value), 'utf8').digest('hex')}`;
118
+ }
119
+ /** Descriptive alias for callers that prefer the content-identity wording. */
120
+ export const digestPortableGraph = portableGraphDigest;
121
+ function validateDecisionRecords(graph, decisions) {
122
+ const decisionEntities = new Set(graph.entities.filter((entity) => entity.type === 'decision').map((entity) => entity.id));
123
+ const records = new Map();
124
+ decisions.forEach((value, index) => {
125
+ if (!isPlainRecord(value))
126
+ throw new Error(`PORTABLE-GRAPH-DECISION-SHAPE at decisions[${index}]`);
127
+ requireNonEmptyString(value.id, `decisions[${index}].id`);
128
+ requireNonEmptyString(value.title, `decisions[${index}].title`);
129
+ requireNonEmptyString(value.decision, `decisions[${index}].decision`);
130
+ if (records.has(value.id))
131
+ throw new Error(`PORTABLE-GRAPH-DECISION-ID-UNIQUE: duplicate decision ID ${value.id}`);
132
+ records.set(value.id, value);
133
+ optionalString(value.topic, `decisions[${index}].topic`);
134
+ optionalString(value.rationale, `decisions[${index}].rationale`);
135
+ optionalString(value.updatedAt, `decisions[${index}].updatedAt`);
136
+ if (value.effectiveAt !== undefined) {
137
+ if (typeof value.effectiveAt !== 'string' || !isRfc3339(value.effectiveAt)) {
138
+ throw new Error(`PORTABLE-GRAPH-DECISION-DATE at decisions[${index}].effectiveAt`);
139
+ }
140
+ }
141
+ if (value.tags !== undefined && (!Array.isArray(value.tags) || value.tags.some((tag) => typeof tag !== 'string'))) {
142
+ throw new Error(`PORTABLE-GRAPH-DECISION-TAGS at decisions[${index}].tags`);
143
+ }
144
+ if (value.supersedes !== undefined) {
145
+ if (!Array.isArray(value.supersedes) || value.supersedes.some((id) => typeof id !== 'string' || id.trim() === '')) {
146
+ throw new Error(`PORTABLE-GRAPH-DECISION-SUPERSEDES at decisions[${index}].supersedes`);
147
+ }
148
+ const refs = new Set();
149
+ for (const target of value.supersedes) {
150
+ if (refs.has(target))
151
+ throw new Error(`PORTABLE-GRAPH-DECISION-REFERENCE: duplicate supersedes reference ${target}`);
152
+ refs.add(target);
153
+ }
154
+ }
155
+ });
156
+ for (const id of decisionEntities) {
157
+ if (!records.has(id))
158
+ throw new Error(`PORTABLE-GRAPH-DECISION-REFERENCE: missing decision record for Graph entity ${id}`);
159
+ }
160
+ for (const [id, decision] of records) {
161
+ const entity = graph.entities.find((candidate) => candidate.id === id);
162
+ if (!entity || entity.type !== 'decision') {
163
+ throw new Error(`PORTABLE-GRAPH-DECISION-REFERENCE: decision record ${id} has no Graph decision entity`);
164
+ }
165
+ for (const target of decision.supersedes ?? []) {
166
+ if (!records.has(target)) {
167
+ throw new Error(`PORTABLE-GRAPH-DECISION-REFERENCE: decision ${id} supersedes missing decision ${target}`);
168
+ }
169
+ }
170
+ }
171
+ }
172
+ function requireNonEmptyString(value, path) {
173
+ if (typeof value !== 'string' || value.trim() === '')
174
+ throw new Error(`PORTABLE-GRAPH-STRING at ${path}`);
175
+ }
176
+ function optionalString(value, path) {
177
+ if (value !== undefined && typeof value !== 'string')
178
+ throw new Error(`PORTABLE-GRAPH-STRING at ${path}`);
179
+ }
180
+ function isRfc3339(value) {
181
+ return /^(?:\d{4})-(?:\d{2})-(?:\d{2})T(?:\d{2}):(?:\d{2}):(?:\d{2})(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/u.test(value) && Number.isFinite(Date.parse(value));
182
+ }
183
+ function isPlainRecord(value) {
184
+ if (!value || typeof value !== 'object' || Array.isArray(value))
185
+ return false;
186
+ const prototype = Object.getPrototypeOf(value);
187
+ return prototype === Object.prototype || prototype === null;
188
+ }
189
+ function assertJsonValue(value, path, active, depth) {
190
+ if (depth > PORTABLE_GRAPH_MAX_JSON_DEPTH)
191
+ throw new Error(`PORTABLE-GRAPH-BOUND: JSON depth exceeds ${PORTABLE_GRAPH_MAX_JSON_DEPTH} at ${path}`);
192
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
193
+ return;
194
+ if (typeof value === 'number') {
195
+ if (!Number.isFinite(value))
196
+ throw new Error(`PORTABLE-GRAPH-JSON: non-finite number at ${path}`);
197
+ return;
198
+ }
199
+ if (typeof value !== 'object')
200
+ throw new Error(`PORTABLE-GRAPH-JSON: non-JSON value at ${path}`);
201
+ if (active.has(value))
202
+ throw new Error(`PORTABLE-GRAPH-JSON: cyclic value at ${path}`);
203
+ active.add(value);
204
+ try {
205
+ if (Array.isArray(value)) {
206
+ for (let index = 0; index < value.length; index += 1) {
207
+ if (!(index in value))
208
+ throw new Error(`PORTABLE-GRAPH-JSON: sparse array at ${path}[${index}]`);
209
+ assertJsonValue(value[index], `${path}[${index}]`, active, depth + 1);
210
+ }
211
+ return;
212
+ }
213
+ if (!isPlainRecord(value))
214
+ throw new Error(`PORTABLE-GRAPH-JSON: value at ${path} must be a plain JSON object`);
215
+ for (const key of Object.getOwnPropertyNames(value)) {
216
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
217
+ if (!descriptor || !('value' in descriptor))
218
+ throw new Error(`PORTABLE-GRAPH-JSON: accessor at ${path}.${key}`);
219
+ assertJsonValue(descriptor.value, `${path}.${key}`, active, depth + 1);
220
+ }
221
+ if (Object.getOwnPropertySymbols(value).length > 0)
222
+ throw new Error(`PORTABLE-GRAPH-JSON: symbol key at ${path}`);
223
+ }
224
+ finally {
225
+ active.delete(value);
226
+ }
227
+ }
228
+ function stableJson(value, depth) {
229
+ if (depth > PORTABLE_GRAPH_MAX_JSON_DEPTH)
230
+ throw new Error(`PORTABLE-GRAPH-BOUND: JSON depth exceeds ${PORTABLE_GRAPH_MAX_JSON_DEPTH}`);
231
+ if (value === null)
232
+ return 'null';
233
+ if (typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number')
234
+ return JSON.stringify(value);
235
+ if (Array.isArray(value))
236
+ return `[${value.map((item) => stableJson(item, depth + 1)).join(',')}]`;
237
+ const record = value;
238
+ const pairs = Object.getOwnPropertyNames(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key], depth + 1)}`);
239
+ return `{${pairs.join(',')}}`;
240
+ }
package/dist/server.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
2
  export declare const toolDefinitions: readonly [{
3
3
  readonly name: "get_context";
4
- readonly description: "Return initial AI context from local Graph and Personal KG canonical files.";
4
+ readonly description: "Return an initial owner context snapshot. For a question needing related judgments or evidence, use search with an explicit relation plan; this snapshot is not a retrieval answer.";
5
5
  readonly inputSchema: {
6
6
  readonly type: "object";
7
7
  readonly properties: {
@@ -35,19 +35,58 @@ export declare const toolDefinitions: readonly [{
35
35
  };
36
36
  }, {
37
37
  readonly name: "search";
38
- readonly description: "Search all canonical local stores: Graph entities, Personal KG, relationships, and decisions. Use this for people and projects.";
38
+ readonly description: "Find canonical Graph candidates, follow explicit typed relations, and return evidence and missing information. Use seedIds from resolve_entity/list_entities or previous results and steps from the observed relation catalog. Configured embeddings enable semantic discovery; without them discovery is lexical and reported as limited. Never treat similarity as proof or empty results as absence. Personal KG has its own search tool.";
39
39
  readonly inputSchema: {
40
40
  readonly type: "object";
41
41
  readonly required: readonly ["query"];
42
+ readonly additionalProperties: false;
42
43
  readonly properties: {
43
44
  readonly dataDir: {
44
45
  readonly type: "string";
45
46
  };
47
+ readonly asOf: {
48
+ readonly type: "string";
49
+ readonly format: "date-time";
50
+ readonly description: "Compatibility alias for as_of.";
51
+ };
46
52
  readonly query: {
47
53
  readonly type: "string";
54
+ readonly minLength: 1;
55
+ };
56
+ readonly seedIds: {
57
+ readonly type: "array";
58
+ readonly minItems: 1;
59
+ readonly maxItems: 10;
60
+ readonly items: {
61
+ readonly type: "string";
62
+ };
63
+ readonly description: "Canonical IDs chosen by the calling model; no embeddings needed.";
64
+ };
65
+ readonly steps: {
66
+ readonly type: "array";
67
+ readonly minItems: 1;
68
+ readonly maxItems: 3;
69
+ readonly items: {
70
+ readonly type: "object";
71
+ readonly additionalProperties: false;
72
+ readonly required: readonly ["relation", "direction"];
73
+ readonly properties: {
74
+ readonly relation: {
75
+ readonly enum: readonly ["member_of", "participates_in", "accountable_for", "owned_by", "governs", "supersedes"];
76
+ };
77
+ readonly direction: {
78
+ readonly enum: readonly ["incoming", "outgoing"];
79
+ };
80
+ readonly targetType: {
81
+ readonly enum: readonly ["person", "org", "project", "decision"];
82
+ };
83
+ };
84
+ };
48
85
  };
49
86
  readonly limit: {
50
- readonly type: "number";
87
+ readonly type: "integer";
88
+ readonly minimum: 1;
89
+ readonly maximum: 50;
51
90
  };
52
91
  readonly project: {
53
92
  readonly type: "string";
package/dist/server.js CHANGED
@@ -1,4 +1,7 @@
1
+ import { createOrganizationGraphConfig, createOrganizationGraphClient } from './organization-graph.js';
1
2
  import { createHash } from 'node:crypto';
3
+ import { retrieveGraph } from './graph-retrieval.js';
4
+ import { createEmbeddingProviderFromEnv } from './embedding-provider.js';
2
5
  import { readFile } from 'node:fs/promises';
3
6
  import { join } from 'node:path';
4
7
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
@@ -22,8 +25,16 @@ const argsSchema = z.object({
22
25
  ontologyVersion: z.string().optional(),
23
26
  asOf: z.string().datetime({ offset: true }).optional(),
24
27
  project: z.string().min(1).optional(),
25
- as_of: z.string().datetime({ offset: true }).optional()
28
+ as_of: z.string().datetime({ offset: true }).optional(),
29
+ seedIds: z.array(z.string().min(1)).min(1).max(10).optional(),
30
+ steps: z.array(z.object({
31
+ relation: z.enum(['member_of', 'participates_in', 'accountable_for', 'owned_by', 'governs', 'supersedes']),
32
+ direction: z.enum(['incoming', 'outgoing']),
33
+ targetType: z.enum(['person', 'org', 'project', 'decision']).optional()
34
+ }).strict()).min(1).max(3).optional(),
35
+ mode: z.never().optional()
26
36
  });
37
+ const searchArgsSchema = argsSchema.pick({ dataDir: true, query: true, limit: true, project: true, as_of: true, asOf: true, seedIds: true, steps: true, mode: true }).strict();
27
38
  const mentionSpanSchema = z.object({
28
39
  start: z.number().int().nonnegative(),
29
40
  end: z.number().int().positive()
@@ -117,7 +128,7 @@ const connectedSchemas = {
117
128
  export const toolDefinitions = [
118
129
  {
119
130
  name: 'get_context',
120
- description: 'Return initial AI context from local Graph and Personal KG canonical files.',
131
+ description: 'Return an initial owner context snapshot. For a question needing related judgments or evidence, use search with an explicit relation plan; this snapshot is not a retrieval answer.',
121
132
  inputSchema: {
122
133
  type: 'object',
123
134
  properties: {
@@ -140,14 +151,24 @@ export const toolDefinitions = [
140
151
  },
141
152
  {
142
153
  name: 'search',
143
- description: 'Search all canonical local stores: Graph entities, Personal KG, relationships, and decisions. Use this for people and projects.',
154
+ description: 'Find canonical Graph candidates, follow explicit typed relations, and return evidence and missing information. Use seedIds from resolve_entity/list_entities or previous results and steps from the observed relation catalog. Configured embeddings enable semantic discovery; without them discovery is lexical and reported as limited. Never treat similarity as proof or empty results as absence. Personal KG has its own search tool.',
144
155
  inputSchema: {
145
156
  type: 'object',
146
157
  required: ['query'],
158
+ additionalProperties: false,
147
159
  properties: {
148
160
  dataDir: { type: 'string' },
149
- query: { type: 'string' },
150
- limit: { type: 'number' },
161
+ asOf: { type: 'string', format: 'date-time', description: 'Compatibility alias for as_of.' },
162
+ query: { type: 'string', minLength: 1 },
163
+ seedIds: { type: 'array', minItems: 1, maxItems: 10, items: { type: 'string' }, description: 'Canonical IDs chosen by the calling model; no embeddings needed.' },
164
+ steps: { type: 'array', minItems: 1, maxItems: 3, items: {
165
+ type: 'object', additionalProperties: false, required: ['relation', 'direction'], properties: {
166
+ relation: { enum: ['member_of', 'participates_in', 'accountable_for', 'owned_by', 'governs', 'supersedes'] },
167
+ direction: { enum: ['incoming', 'outgoing'] },
168
+ targetType: { enum: ['person', 'org', 'project', 'decision'] }
169
+ }
170
+ } },
171
+ limit: { type: 'integer', minimum: 1, maximum: 50 },
151
172
  project: { type: 'string', description: 'Optional canonical project ID, name, or alias.' },
152
173
  as_of: { type: 'string', format: 'date-time', description: 'RFC 3339 validity instant. Defaults to now.' }
153
174
  }
@@ -330,6 +351,15 @@ export const toolDefinitions = [
330
351
  }
331
352
  }
332
353
  ];
354
+ let embeddingProvider;
355
+ let embeddingProviderInitialized = false;
356
+ function configuredEmbeddingProvider() {
357
+ if (!embeddingProviderInitialized) {
358
+ embeddingProvider = createEmbeddingProviderFromEnv();
359
+ embeddingProviderInitialized = true;
360
+ }
361
+ return embeddingProvider;
362
+ }
333
363
  export async function callBrainbaseTool(name, rawArgs = {}) {
334
364
  if (name in connectedSchemas) {
335
365
  return callConnectedOnboardingTool(name, rawArgs);
@@ -337,7 +367,7 @@ export async function callBrainbaseTool(name, rawArgs = {}) {
337
367
  if (name === 'resolve_entity') {
338
368
  return callResolveEntityTool(rawArgs);
339
369
  }
340
- const args = argsSchema.parse(rawArgs ?? {});
370
+ const args = (name === 'search' ? searchArgsSchema : argsSchema).parse(rawArgs ?? {});
341
371
  const dataDir = resolveDataDir(args.dataDir);
342
372
  switch (name) {
343
373
  case 'get_ontology':
@@ -400,6 +430,13 @@ export async function callBrainbaseTool(name, rawArgs = {}) {
400
430
  case 'ontology_impact':
401
431
  return getOntologyImpact(args.fromVersion);
402
432
  }
433
+ if (name === 'search') {
434
+ const organization = createOrganizationGraphConfig();
435
+ if (organization) {
436
+ return createOrganizationGraphClient(organization).search({ query: args.query, limit: args.limit,
437
+ project: args.project, asOf: args.as_of ?? args.asOf, seedIds: args.seedIds, steps: args.steps });
438
+ }
439
+ }
403
440
  const os = await loadPersonalOs(dataDir);
404
441
  switch (name) {
405
442
  case 'get_context':
@@ -410,7 +447,15 @@ export async function callBrainbaseTool(name, rawArgs = {}) {
410
447
  if (!args.query) {
411
448
  throw new Error('search requires query');
412
449
  }
413
- return { results: searchAll(os, args.query, args.limit, { project: args.project, asOf: args.as_of ?? args.asOf }) };
450
+ if (os.graph.version !== 2) {
451
+ if (args.seedIds || args.steps)
452
+ throw new Error('graph_retrieval_migration_required');
453
+ return { results: searchAll(os, args.query, args.limit, { project: args.project, asOf: args.as_of ?? args.asOf }),
454
+ status: 'migration_required', discovery: 'legacy_lexical', absenceConfirmed: false,
455
+ sufficiency: 'insufficient', partialReasons: ['canonical_graph_v2_required'] };
456
+ }
457
+ return retrieveGraph(os, { query: args.query, limit: args.limit, project: args.project,
458
+ asOf: args.as_of ?? args.asOf, seedIds: args.seedIds, steps: args.steps }, args.seedIds ? undefined : configuredEmbeddingProvider());
414
459
  case 'search_personal_kg':
415
460
  if (!args.query) {
416
461
  throw new Error('search_personal_kg requires query');
@@ -3,12 +3,42 @@ title: Brainbase Judgment DAG Milestones
3
3
  status: active
4
4
  date: 2026-08-20
5
5
  scope: OSS Brainbase
6
+ governed_by_repository: Unson-LLC/brainbase-unson
7
+ governed_by_path: docs/management/milestones/brainbase-program-master-roadmap.md
8
+ governed_by_machine_path: docs/management/milestones/brainbase-program-master-roadmap.json
9
+ governed_by_commit: 18544f58a2a0298d97eab45de2f05544bed48a43
10
+ governed_by_markdown_sha256: 167afb6d3fc57198c9f5ffca06fbafea968a5435af04f44b55017199b6d859fe
11
+ governed_by_json_sha256: f3e6e023060ef3976f367ed7efa62ac57f7091f517ea1ca1d13824d9e6ca429f
12
+ program_packages:
13
+ - R0
14
+ - J0
15
+ - G0
16
+ - R1
17
+ - D0
18
+ - P0
19
+ - C0
6
20
  ---
7
21
 
8
22
  # Brainbase Judgment DAG Milestones
9
23
 
10
24
  This roadmap replaces any implicit assumption that OSS Brainbase stops at memory retrieval. The next milestones make the shared Judgment DAG core real without prematurely building a complete enterprise ontology.
11
25
 
26
+ ## Program governance
27
+
28
+ この文書はOSS Judgment DAGのcomponent roadmapであり、cross-repositoryの依存順、開始条件、完了条件は`Unson-LLC/brainbase-unson`の`docs/management/milestones/brainbase-program-master-roadmap.md`に従う。受理した正本snapshotはcommit `18544f58a2a0298d97eab45de2f05544bed48a43`へ固定し、Markdownとmachine-readable JSONのpath・SHA-256は`contracts/judgment-dag/source-lock.json`を正本とする。対応するProgram work packageは R0 / J0 / G0 / R1 / D0 / P0 / C0 である。競合時は固定したProgram Master Roadmapを優先し、この文書のM0〜M6をProgram全体の順序や完了判定へ読み替えない。新しいProgram revisionへ追随する場合は、commitと両content hashを明示的に更新し、契約生成・検証・独立レビューを再実行する。
29
+
30
+ | Component milestone | Program work package |
31
+ |---|---|
32
+ | M0 Architecture lock | R0 + J0 |
33
+ | M1 Local DAG kernel | J0 |
34
+ | M2 Human + Agent judgment runners | G0 |
35
+ | M3 Replay and evaluation | R1 |
36
+ | M4 Brainbase Deployment dogfood | D0 |
37
+ | M5 Scope promotion | P0 |
38
+ | M6 Organization-ready primitives | G0 + C0 |
39
+
40
+ Program statusには `planned` / `contract_ready` / `implementing` / `verified` / `production_proven` / `done` の6語彙だけを使う。hard dependencyを満たさないmilestoneを`done`にしない。文書のmergeだけを実装完了または`done`と扱わず、未実施・未収集・staleな証跡をpassや0件へ丸めない。
41
+
12
42
  ## M0 — Architecture lock
13
43
 
14
44
  Goal: freeze semantic boundaries before implementation.
@@ -140,4 +170,4 @@ The roadmap is not complete merely because graph/node counts increase. Measure:
140
170
  4. **Outcome calibration** — do judgment versions improve against declared goals?
141
171
  5. **Promotion quality** — are reusable judgments correctly distinguished from case-specific decisions?
142
172
 
143
- The primary dogfood KPI is **expert escalation count per deployment**, not total stored knowledge.
173
+ The primary dogfood KPI is **expert escalation count per deployment**, not total stored knowledge.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@unson/brainbase-mcp",
3
- "version": "0.4.1",
4
- "description": "Local-first Brainbase MCP for preserving decision context and helping humans and AI work from the same judgment criteria.",
3
+ "version": "0.5.0",
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",
7
7
  "repository": {
@@ -19,11 +19,21 @@
19
19
  "types": "./dist/judgment-dag.d.ts",
20
20
  "import": "./dist/judgment-dag.js"
21
21
  },
22
+ "./judgment-value-proof": {
23
+ "types": "./dist/judgment-value-proof.d.ts",
24
+ "import": "./dist/judgment-value-proof.js"
25
+ },
26
+ "./portable-graph": {
27
+ "types": "./dist/portable-graph.d.ts",
28
+ "import": "./dist/portable-graph.js"
29
+ },
22
30
  "./dist/*": "./dist/*",
23
31
  "./contracts/judgment-dag/schema.json": "./contracts/judgment-dag/schema.json",
24
32
  "./contracts/judgment-dag/fixture.json": "./contracts/judgment-dag/fixture.json",
25
33
  "./contracts/judgment-dag/source-lock.json": "./contracts/judgment-dag/source-lock.json",
26
34
  "./contracts/judgment-dag/digest.json": "./contracts/judgment-dag/digest.json",
35
+ "./contracts/judgment-value-proof/schema.json": "./contracts/judgment-value-proof/schema.json",
36
+ "./contracts/judgment-value-proof/fixture.json": "./contracts/judgment-value-proof/fixture.json",
27
37
  "./package.json": "./package.json"
28
38
  },
29
39
  "bin": {
@@ -51,10 +61,10 @@
51
61
  "test:e2e": "vitest run tests/e2e/brainbase-mcp-only-acceptance.spec.ts tests/e2e/story-brainbase-portable-ontology-kernel-acceptance.spec.ts",
52
62
  "docs:dev": "vitepress dev docs",
53
63
  "docs:sync": "node scripts/sync-public-message.mjs --write",
54
- "docs:check": "node scripts/check-public-docs.mjs",
64
+ "docs:check": "node scripts/check-public-docs.mjs && node scripts/check-organization-teaser.mjs",
55
65
  "docs:build": "vitepress build docs",
56
- "docs:smoke": "node scripts/check-built-docs.mjs",
57
- "docs:verify-public": "node scripts/verify-public-site.mjs",
66
+ "docs:smoke": "node scripts/check-built-docs.mjs && node scripts/check-built-organization-teaser.mjs",
67
+ "docs:verify-public": "node scripts/verify-public-site.mjs && node scripts/verify-organization-teaser.mjs",
58
68
  "docs:promotion:plan": "node scripts/promote-public-message.mjs --plan",
59
69
  "docs:promotion:apply": "node scripts/promote-public-message.mjs --apply",
60
70
  "docs:preview": "vitepress preview docs",
@@ -88,5 +98,5 @@
88
98
  "engines": {
89
99
  "node": ">=20"
90
100
  },
91
- "gitHead": "b6210adfb43cf4321148e06dc48e94f200b805a5"
101
+ "gitHead": "00ba08ebc953a3f8db66e9b624eceebd331c1e24"
92
102
  }