@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/dist/projects.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { buildCanonicalEdge } from './canonical-edge-builder.js';
1
2
  export function parseProjectSource(value) {
2
3
  const [areaRaw, labelRaw, refRaw] = value.split('|').map((part) => part.trim());
3
4
  const area = parseProjectSourceArea(areaRaw);
@@ -74,6 +75,43 @@ export function buildProjectRegistrationPlan(input) {
74
75
  tags: ['project', 'principle'],
75
76
  updatedAt: now
76
77
  }));
78
+ const decisionEntities = decisions.map((decision) => ({
79
+ id: decision.id,
80
+ type: 'decision',
81
+ name: decision.title,
82
+ summary: decision.decision,
83
+ tags: decision.tags,
84
+ metadata: {
85
+ rationale: decision.rationale,
86
+ projectId
87
+ }
88
+ }));
89
+ const canonicalEntities = [
90
+ { ...projectEntity, type: 'project' },
91
+ ...stakeholderEntities.map((entity) => ({ ...entity, type: 'person' })),
92
+ ...decisionEntities
93
+ ];
94
+ const participationEdges = stakeholders.map((stakeholder) => {
95
+ const personId = `person-${stableHash(stakeholder.person)}`;
96
+ return buildCanonicalEdge({
97
+ fromId: personId,
98
+ relation: 'participates_in',
99
+ toId: projectId,
100
+ role: stakeholder.role,
101
+ context: stakeholder.context,
102
+ provenance: {
103
+ sourceKind: 'onboarding',
104
+ sourceId: `relationship-${stableHash(`${name}|${stakeholder.person}|${stakeholder.context}`)}`
105
+ }
106
+ });
107
+ });
108
+ const governanceEdges = decisions.map((decision) => buildCanonicalEdge({
109
+ fromId: decision.id,
110
+ relation: 'governs',
111
+ toId: projectId,
112
+ context: decision.decision,
113
+ provenance: { sourceKind: 'onboarding', sourceId: decision.id }
114
+ }));
77
115
  return {
78
116
  goal: 'Review project registration before promoting it into canonical Brainbase SSOT.',
79
117
  canonicalWrites: false,
@@ -91,6 +129,8 @@ export function buildProjectRegistrationPlan(input) {
91
129
  stakeholders,
92
130
  writes: {
93
131
  graphEntities: [projectEntity, ...stakeholderEntities],
132
+ canonicalEntities,
133
+ canonicalEdges: [...participationEdges, ...governanceEdges],
94
134
  relationships,
95
135
  personalKg,
96
136
  decisions
@@ -0,0 +1,11 @@
1
+ import type { CanonicalEntityKind, CoreRelation } from './types.js';
2
+ export interface CanonicalRelationDefinition {
3
+ id: CoreRelation;
4
+ from: CanonicalEntityKind;
5
+ to: CanonicalEntityKind;
6
+ meaning: string;
7
+ scopeTraversal: 'none' | 'project_direct' | 'project_transitive';
8
+ traversalDirection: 'none' | 'forward' | 'reverse';
9
+ }
10
+ export declare const canonicalRelationRegistry: Readonly<Record<CoreRelation, Readonly<CanonicalRelationDefinition>>>;
11
+ export declare function getCanonicalRelation(relation: string): Readonly<CanonicalRelationDefinition>;
@@ -0,0 +1,66 @@
1
+ const definitions = {
2
+ member_of: {
3
+ id: 'member_of',
4
+ from: 'person',
5
+ to: 'org',
6
+ meaning: 'A person is a member of an organization.',
7
+ scopeTraversal: 'none',
8
+ traversalDirection: 'none'
9
+ },
10
+ participates_in: {
11
+ id: 'participates_in',
12
+ from: 'person',
13
+ to: 'project',
14
+ meaning: 'A person participates in a project.',
15
+ scopeTraversal: 'project_direct',
16
+ traversalDirection: 'forward'
17
+ },
18
+ accountable_for: {
19
+ id: 'accountable_for',
20
+ from: 'person',
21
+ to: 'project',
22
+ meaning: 'A person is accountable for a project outcome or decision.',
23
+ scopeTraversal: 'project_direct',
24
+ traversalDirection: 'forward'
25
+ },
26
+ owned_by: {
27
+ id: 'owned_by',
28
+ from: 'project',
29
+ to: 'org',
30
+ meaning: 'A project is owned by an organization.',
31
+ scopeTraversal: 'project_direct',
32
+ traversalDirection: 'reverse'
33
+ },
34
+ governs: {
35
+ id: 'governs',
36
+ from: 'decision',
37
+ to: 'project',
38
+ meaning: 'A durable decision or principle governs a project.',
39
+ scopeTraversal: 'project_direct',
40
+ traversalDirection: 'forward'
41
+ },
42
+ supersedes: {
43
+ id: 'supersedes',
44
+ from: 'decision',
45
+ to: 'decision',
46
+ meaning: 'A durable decision explicitly supersedes another decision.',
47
+ scopeTraversal: 'project_transitive',
48
+ traversalDirection: 'forward'
49
+ }
50
+ };
51
+ export const canonicalRelationRegistry = deepFreeze(definitions);
52
+ export function getCanonicalRelation(relation) {
53
+ const definition = canonicalRelationRegistry[relation];
54
+ if (!definition) {
55
+ throw new Error(`ONTOLOGY-RELATION-UNKNOWN: unsupported canonical relation ${JSON.stringify(relation)}`);
56
+ }
57
+ return definition;
58
+ }
59
+ function deepFreeze(value) {
60
+ if (value && typeof value === 'object' && !Object.isFrozen(value)) {
61
+ Object.freeze(value);
62
+ for (const nested of Object.values(value))
63
+ deepFreeze(nested);
64
+ }
65
+ return value;
66
+ }
package/dist/server.d.ts CHANGED
@@ -8,6 +8,15 @@ export declare const toolDefinitions: readonly [{
8
8
  readonly dataDir: {
9
9
  readonly type: "string";
10
10
  };
11
+ readonly project: {
12
+ readonly type: "string";
13
+ readonly description: "Optional canonical project ID, name, or alias.";
14
+ };
15
+ readonly as_of: {
16
+ readonly type: "string";
17
+ readonly format: "date-time";
18
+ readonly description: "RFC 3339 validity instant. Defaults to now.";
19
+ };
11
20
  };
12
21
  };
13
22
  }, {
@@ -40,6 +49,15 @@ export declare const toolDefinitions: readonly [{
40
49
  readonly limit: {
41
50
  readonly type: "number";
42
51
  };
52
+ readonly project: {
53
+ readonly type: "string";
54
+ readonly description: "Optional canonical project ID, name, or alias.";
55
+ };
56
+ readonly as_of: {
57
+ readonly type: "string";
58
+ readonly format: "date-time";
59
+ readonly description: "RFC 3339 validity instant. Defaults to now.";
60
+ };
43
61
  };
44
62
  };
45
63
  }, {
@@ -380,7 +398,7 @@ export declare const toolDefinitions: readonly [{
380
398
  readonly type: "string";
381
399
  };
382
400
  readonly ontologyVersion: {
383
- readonly enum: readonly ["0.0.0", "1.0.0"];
401
+ readonly enum: readonly ["0.0.0", "1.0.0", "2.0.0"];
384
402
  };
385
403
  };
386
404
  };
@@ -398,7 +416,7 @@ export declare const toolDefinitions: readonly [{
398
416
  readonly format: "date-time";
399
417
  };
400
418
  readonly ontologyVersion: {
401
- readonly enum: readonly ["0.0.0", "1.0.0"];
419
+ readonly enum: readonly ["0.0.0", "1.0.0", "2.0.0"];
402
420
  };
403
421
  };
404
422
  };
@@ -413,6 +431,72 @@ export declare const toolDefinitions: readonly [{
413
431
  };
414
432
  };
415
433
  };
434
+ }, {
435
+ readonly name: "resolve_entity";
436
+ readonly description: "Resolve mentions in text to canonical Graph v2 entity IDs and return a privacy-safe evidence receipt.";
437
+ readonly inputSchema: {
438
+ readonly type: "object";
439
+ readonly required: readonly ["text", "asOf"];
440
+ readonly additionalProperties: false;
441
+ readonly properties: {
442
+ readonly dataDir: {
443
+ readonly type: "string";
444
+ readonly description: "Optional Personal OS directory.";
445
+ };
446
+ readonly text: {
447
+ readonly type: "string";
448
+ readonly description: "Text whose entity mentions should be resolved. The text is hashed, not stored in the receipt.";
449
+ };
450
+ readonly asOf: {
451
+ readonly type: "string";
452
+ readonly format: "date-time";
453
+ readonly description: "RFC 3339 instant used for temporal entity and edge validity.";
454
+ };
455
+ readonly mentionSpans: {
456
+ readonly type: "array";
457
+ readonly items: {
458
+ readonly type: "object";
459
+ readonly required: readonly ["start", "end"];
460
+ readonly additionalProperties: false;
461
+ readonly properties: {
462
+ readonly start: {
463
+ readonly type: "integer";
464
+ readonly minimum: 0;
465
+ };
466
+ readonly end: {
467
+ readonly type: "integer";
468
+ readonly minimum: 1;
469
+ };
470
+ };
471
+ };
472
+ };
473
+ readonly projectScope: {
474
+ readonly type: "object";
475
+ readonly required: readonly ["projectIds"];
476
+ readonly additionalProperties: false;
477
+ readonly properties: {
478
+ readonly projectIds: {
479
+ readonly type: "array";
480
+ readonly minItems: 1;
481
+ readonly items: {
482
+ readonly type: "string";
483
+ readonly minLength: 1;
484
+ };
485
+ };
486
+ readonly policy: {
487
+ readonly enum: readonly ["strict", "prefer_project", "allow_global_fallback"];
488
+ };
489
+ };
490
+ };
491
+ readonly entityTypes: {
492
+ readonly type: "array";
493
+ readonly minItems: 1;
494
+ readonly items: {
495
+ readonly enum: readonly ["person", "org", "project", "decision"];
496
+ };
497
+ };
498
+ };
499
+ };
416
500
  }];
417
501
  export declare function callBrainbaseTool(name: string, rawArgs?: unknown): Promise<unknown>;
418
502
  export declare function createServer(): Server;
package/dist/server.js CHANGED
@@ -1,8 +1,13 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
1
4
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
5
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
6
  import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
4
7
  import { z } from 'zod';
5
8
  import { ConnectedOnboardingRuntime } from './connected-onboarding.js';
9
+ import { validateCanonicalGraph } from './canonical-graph.js';
10
+ import { resolveText } from './entity-resolution.js';
6
11
  import { resolveDataDir } from './paths.js';
7
12
  import { auditPersonalOsDirectory } from './ontology-ssot.js';
8
13
  import { getOntologyImpact, inferPersonalOs, portableOntology, resolveOntologyVersion } from './ontology.js';
@@ -15,7 +20,34 @@ const argsSchema = z.object({
15
20
  type: z.enum(['person', 'org', 'project', 'relationship', 'decision']).optional(),
16
21
  fromVersion: z.string().optional(),
17
22
  ontologyVersion: z.string().optional(),
18
- asOf: z.string().datetime({ offset: true }).optional()
23
+ asOf: z.string().datetime({ offset: true }).optional(),
24
+ project: z.string().min(1).optional(),
25
+ as_of: z.string().datetime({ offset: true }).optional()
26
+ });
27
+ const mentionSpanSchema = z.object({
28
+ start: z.number().int().nonnegative(),
29
+ end: z.number().int().positive()
30
+ }).strict().refine((span) => span.end > span.start, { message: 'mention span end must be after start' });
31
+ const resolveEntitySchema = z.object({
32
+ dataDir: z.string().optional(),
33
+ text: z.string(),
34
+ asOf: z.string().datetime({ offset: true }),
35
+ mentionSpans: z.array(mentionSpanSchema).optional(),
36
+ projectScope: z.object({
37
+ projectIds: z.array(z.string().min(1)).min(1),
38
+ policy: z.enum(['strict', 'prefer_project', 'allow_global_fallback']).optional()
39
+ }).strict().optional(),
40
+ entityTypes: z.array(z.enum(['person', 'org', 'project', 'decision'])).min(1).optional()
41
+ }).strict().superRefine((value, context) => {
42
+ for (const [index, span] of (value.mentionSpans ?? []).entries()) {
43
+ if (span.end > value.text.length) {
44
+ context.addIssue({
45
+ code: z.ZodIssueCode.custom,
46
+ path: ['mentionSpans', index, 'end'],
47
+ message: 'mention span must be within text'
48
+ });
49
+ }
50
+ }
19
51
  });
20
52
  const sourceInventorySchema = z.object({
21
53
  id: z.string(),
@@ -89,7 +121,9 @@ export const toolDefinitions = [
89
121
  inputSchema: {
90
122
  type: 'object',
91
123
  properties: {
92
- dataDir: { type: 'string' }
124
+ dataDir: { type: 'string' },
125
+ project: { type: 'string', description: 'Optional canonical project ID, name, or alias.' },
126
+ as_of: { type: 'string', format: 'date-time', description: 'RFC 3339 validity instant. Defaults to now.' }
93
127
  }
94
128
  }
95
129
  },
@@ -113,7 +147,9 @@ export const toolDefinitions = [
113
147
  properties: {
114
148
  dataDir: { type: 'string' },
115
149
  query: { type: 'string' },
116
- limit: { type: 'number' }
150
+ limit: { type: 'number' },
151
+ project: { type: 'string', description: 'Optional canonical project ID, name, or alias.' },
152
+ as_of: { type: 'string', format: 'date-time', description: 'RFC 3339 validity instant. Defaults to now.' }
117
153
  }
118
154
  }
119
155
  },
@@ -230,7 +266,7 @@ export const toolDefinitions = [
230
266
  type: 'object',
231
267
  properties: {
232
268
  dataDir: { type: 'string' },
233
- ontologyVersion: { enum: ['0.0.0', '1.0.0'] }
269
+ ontologyVersion: { enum: ['0.0.0', '1.0.0', '2.0.0'] }
234
270
  }
235
271
  }
236
272
  },
@@ -242,7 +278,7 @@ export const toolDefinitions = [
242
278
  properties: {
243
279
  dataDir: { type: 'string' },
244
280
  asOf: { type: 'string', format: 'date-time' },
245
- ontologyVersion: { enum: ['0.0.0', '1.0.0'] }
281
+ ontologyVersion: { enum: ['0.0.0', '1.0.0', '2.0.0'] }
246
282
  }
247
283
  }
248
284
  },
@@ -255,12 +291,52 @@ export const toolDefinitions = [
255
291
  fromVersion: { type: 'string' }
256
292
  }
257
293
  }
294
+ },
295
+ {
296
+ name: 'resolve_entity',
297
+ description: 'Resolve mentions in text to canonical Graph v2 entity IDs and return a privacy-safe evidence receipt.',
298
+ inputSchema: {
299
+ type: 'object',
300
+ required: ['text', 'asOf'],
301
+ additionalProperties: false,
302
+ properties: {
303
+ dataDir: { type: 'string', description: 'Optional Personal OS directory.' },
304
+ text: { type: 'string', description: 'Text whose entity mentions should be resolved. The text is hashed, not stored in the receipt.' },
305
+ asOf: { type: 'string', format: 'date-time', description: 'RFC 3339 instant used for temporal entity and edge validity.' },
306
+ mentionSpans: {
307
+ type: 'array',
308
+ items: {
309
+ type: 'object',
310
+ required: ['start', 'end'],
311
+ additionalProperties: false,
312
+ properties: { start: { type: 'integer', minimum: 0 }, end: { type: 'integer', minimum: 1 } }
313
+ }
314
+ },
315
+ projectScope: {
316
+ type: 'object',
317
+ required: ['projectIds'],
318
+ additionalProperties: false,
319
+ properties: {
320
+ projectIds: { type: 'array', minItems: 1, items: { type: 'string', minLength: 1 } },
321
+ policy: { enum: ['strict', 'prefer_project', 'allow_global_fallback'] }
322
+ }
323
+ },
324
+ entityTypes: {
325
+ type: 'array',
326
+ minItems: 1,
327
+ items: { enum: ['person', 'org', 'project', 'decision'] }
328
+ }
329
+ }
330
+ }
258
331
  }
259
332
  ];
260
333
  export async function callBrainbaseTool(name, rawArgs = {}) {
261
334
  if (name in connectedSchemas) {
262
335
  return callConnectedOnboardingTool(name, rawArgs);
263
336
  }
337
+ if (name === 'resolve_entity') {
338
+ return callResolveEntityTool(rawArgs);
339
+ }
264
340
  const args = argsSchema.parse(rawArgs ?? {});
265
341
  const dataDir = resolveDataDir(args.dataDir);
266
342
  switch (name) {
@@ -316,21 +392,25 @@ export async function callBrainbaseTool(name, rawArgs = {}) {
316
392
  ...portableOntology
317
393
  };
318
394
  case 'audit_ontology':
319
- return auditPersonalOsDirectory(dataDir, { ontologyVersion: resolveOntologyVersion(args.ontologyVersion) });
395
+ return auditPersonalOsDirectory(dataDir, {
396
+ ontologyVersion: args.ontologyVersion === undefined
397
+ ? undefined
398
+ : resolveOntologyVersion(args.ontologyVersion)
399
+ });
320
400
  case 'ontology_impact':
321
401
  return getOntologyImpact(args.fromVersion);
322
402
  }
323
403
  const os = await loadPersonalOs(dataDir);
324
404
  switch (name) {
325
405
  case 'get_context':
326
- return getContext(os);
406
+ return getContext(os, { project: args.project, asOf: args.as_of ?? args.asOf });
327
407
  case 'list_entities':
328
408
  return listEntities(os, args.type);
329
409
  case 'search':
330
410
  if (!args.query) {
331
411
  throw new Error('search requires query');
332
412
  }
333
- return { results: searchAll(os, args.query, args.limit) };
413
+ return { results: searchAll(os, args.query, args.limit, { project: args.project, asOf: args.as_of ?? args.asOf }) };
334
414
  case 'search_personal_kg':
335
415
  if (!args.query) {
336
416
  throw new Error('search_personal_kg requires query');
@@ -341,12 +421,81 @@ export async function callBrainbaseTool(name, rawArgs = {}) {
341
421
  case 'infer_decisions':
342
422
  return inferPersonalOs(os, {
343
423
  asOf: args.asOf,
344
- ontologyVersion: resolveOntologyVersion(args.ontologyVersion)
424
+ ontologyVersion: args.ontologyVersion === undefined
425
+ ? undefined
426
+ : resolveOntologyVersion(args.ontologyVersion)
345
427
  });
346
428
  default:
347
429
  throw new Error(`Unknown tool: ${name}`);
348
430
  }
349
431
  }
432
+ async function callResolveEntityTool(rawArgs) {
433
+ const args = resolveEntitySchema.parse(rawArgs ?? {});
434
+ const dataDir = resolveDataDir(args.dataDir);
435
+ const graphPath = join(dataDir, 'graph.json');
436
+ let serialized;
437
+ try {
438
+ serialized = await readFile(graphPath, 'utf8');
439
+ }
440
+ catch {
441
+ return blockedResolution(args, 'unavailable', 'graph_unavailable');
442
+ }
443
+ let graph;
444
+ try {
445
+ graph = JSON.parse(serialized);
446
+ }
447
+ catch {
448
+ return blockedResolution(args, 'invalid', 'graph_invalid');
449
+ }
450
+ try {
451
+ validateCanonicalGraph(graph);
452
+ }
453
+ catch {
454
+ return blockedResolution(args, 'invalid', 'graph_invalid');
455
+ }
456
+ if (isGraphVersion(graph, 1)) {
457
+ return {
458
+ status: 'migration_required',
459
+ graphSchemaVersion: 1,
460
+ requiredAction: 'migrate_graph_v2'
461
+ };
462
+ }
463
+ if (!isGraphVersion(graph, 2)) {
464
+ return blockedResolution(args, 'invalid', 'graph_invalid');
465
+ }
466
+ const receipt = resolveText({
467
+ text: args.text,
468
+ ...(args.mentionSpans ? { mentionSpans: args.mentionSpans } : {}),
469
+ ...(args.projectScope ? { projectScope: args.projectScope } : {}),
470
+ asOf: args.asOf,
471
+ ...(args.entityTypes ? { entityTypes: args.entityTypes } : {}),
472
+ source: {
473
+ authority: 'local_graph',
474
+ status: 'complete',
475
+ revision: createHash('sha256').update(serialized).digest('hex'),
476
+ graph: graph
477
+ }
478
+ }).receipt;
479
+ return { status: 'verified', receipt };
480
+ }
481
+ function blockedResolution(args, sourceStatus, issueCode) {
482
+ const receipt = resolveText({
483
+ text: args.text,
484
+ ...(args.mentionSpans ? { mentionSpans: args.mentionSpans } : {}),
485
+ ...(args.projectScope ? { projectScope: args.projectScope } : {}),
486
+ asOf: args.asOf,
487
+ ...(args.entityTypes ? { entityTypes: args.entityTypes } : {}),
488
+ source: {
489
+ authority: 'local_graph',
490
+ status: sourceStatus,
491
+ issues: [{ code: issueCode, message: issueCode }]
492
+ }
493
+ }).receipt;
494
+ return { status: 'unverified', receipt };
495
+ }
496
+ function isGraphVersion(graph, version) {
497
+ return Boolean(graph && typeof graph === 'object' && 'version' in graph && graph.version === version);
498
+ }
350
499
  async function callConnectedOnboardingTool(name, rawArgs) {
351
500
  const schema = connectedSchemas[name];
352
501
  const args = schema.parse(rawArgs ?? {});
package/dist/skills.js CHANGED
@@ -27,11 +27,14 @@ const SKILL_DEFINITIONS = {
27
27
  '2. `brainbase onboard:start --target codex|claude|codecode` を実行し、本人、プロジェクト、関係者、判断基準の最小候補を確認する。',
28
28
  '3. ユーザーが明示的に承認した事実だけを `brainbase onboard:seed` で保存する。',
29
29
  '4. `brainbase onboard:install --target codex|claude|codecode --dry-run` でMCP設定を確認し、承認後に実設定へ反映して対象エージェントを再起動する。',
30
- '5. 新しい実際のエージェントで、Brainbaseの `get_context` と `search` を使うよう明示して最初の現実の依頼を実行する。',
31
- '6. 実回答、使われた保存済み文脈、未確認事項をユーザーへ示し、本人に役立ったかを確認する。ここまでを10分以内の初回価値候補ジャーニーとして測る。',
32
- '7. CLIの `onboard:demo` は必要なら接続前のプレビューに使えるが、CLIサンプルは初回価値の達成証拠にしない。',
33
- '8. 本人が役立つと確認した後だけ、必要に応じてsource診断、追加候補、Skills、ルーティンへ進む。',
34
- '9. 継続利用するプロジェクトの詳細が必要なら、本人確認後に `brainbase onboard:projects --write` で登録する。',
30
+ '5. 新しい実際のエージェントで、Brainbaseの `resolve_entity`、`get_context`、`search` を使うよう明示して最初の現実の依頼を実行する。',
31
+ '6. 内部準備の宣言を挟まず、利用者向け回答の先頭を「覚えていたこと」にする。続けて「つながったこと」「次にできること」の3節を短い箇条書きで示す。確認済みの事実と未確認事項は混ぜず、初回表示に表は使わない。',
32
+ '7. 正規ID、関係経路、Receipt digest、生のtool trace、source file名は「詳細」に分け、ホストが対応する場合は折りたたむ。利用者が求めない限り初回の本文へ並べない。',
33
+ '8. 内部Skillの読み込み、検索の再試行、tool orchestrationは説明しない。失敗によって結果や次の行動が変わる場合だけ示す。',
34
+ '9. その実回答について本人に役立ったかを確認する。取得開始からここまでを10分以内の初回価値候補ジャーニーとして測る。',
35
+ '10. CLIの `onboard:demo` は必要なら接続前のプレビューに使えるが、CLIサンプルは初回価値の達成証拠にしない。',
36
+ '11. 本人が役立つと確認した後だけ、必要に応じてsource診断、追加候補、Skills、ルーティンへ進む。',
37
+ '12. 継続利用するプロジェクトの詳細が必要なら、本人確認後に `brainbase onboard:projects --write` で登録する。',
35
38
  '',
36
39
  '## 安全ルール',
37
40
  '',
@@ -113,7 +116,7 @@ const SKILL_DEFINITIONS = {
113
116
  '3. どの候補idを承認、却下、修正するかをユーザーに確認する。',
114
117
  '4. まず `brainbase onboard:apply --from <candidate-file> --select <id>` でdry-runする。手入力の事実には `brainbase onboard:seed` を使う。',
115
118
  '5. `--write` は明示的な承認後にだけ使い、canonical filesへ書き込む。',
116
- '6. `brainbase doctor` を実行し、必要に応じてBrainbase MCPの `get_context` または `search` で承認済み事実が見えることを確認する。',
119
+ '6. `brainbase doctor` を実行し、Brainbase MCPの `resolve_entity`、`get_context`、`search` で承認済み事実と正規ID接続が見えることを確認する。',
117
120
  '',
118
121
  '## 安全ルール',
119
122
  '',
package/dist/ssot.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type CanonicalGraphMigrationPlan } from './ontology-migration.js';
1
2
  import type { PersonalOs } from './types.js';
2
3
  export declare function initializePersonalOs(dataDir: string): Promise<void>;
3
4
  export declare function loadPersonalOs(dataDir: string): Promise<PersonalOs>;
@@ -11,3 +12,18 @@ export declare function mutatePersonalOsWithSidecar<T>(dataDir: string, sidecarP
11
12
  sidecarContent: string;
12
13
  result: T;
13
14
  }>): Promise<T>;
15
+ export type CanonicalGraphMigrationExecution = CanonicalGraphMigrationPlan & {
16
+ expectedInputDigest: string;
17
+ written: boolean;
18
+ };
19
+ /**
20
+ * Plans or atomically applies the canonical Graph migration.
21
+ *
22
+ * The input is always recovered, read, and planned while holding the same
23
+ * lock used by every canonical SSOT writer. Omitting `write` is a byte-safe
24
+ * preview; a blocked or already-current plan is never committed.
25
+ */
26
+ export declare function migrateCanonicalGraph(dataDir: string, options?: {
27
+ write?: boolean;
28
+ expectedInputDigest?: string;
29
+ }): Promise<CanonicalGraphMigrationExecution>;