@unson/brainbase-mcp 0.1.0 → 0.2.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 +62 -10
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +197 -37
- package/dist/connected-onboarding.d.ts +123 -0
- package/dist/connected-onboarding.js +934 -0
- package/dist/guided-onboarding.d.ts +1 -1
- package/dist/guided-onboarding.js +34 -2
- package/dist/import-extract.d.ts +1 -0
- package/dist/import-extract.js +30 -8
- package/dist/judgment-host.d.ts +205 -0
- package/dist/judgment-host.js +1474 -0
- package/dist/onboarding.d.ts +4 -1
- package/dist/onboarding.js +52 -34
- package/dist/operationalization.js +33 -33
- package/dist/server.d.ts +295 -3
- package/dist/server.js +334 -4
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +32 -0
- package/dist/ssot.d.ts +9 -0
- package/dist/ssot.js +66 -6
- package/dist/tools.js +15 -4
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
+
import { ConnectedOnboardingRuntime } from './connected-onboarding.js';
|
|
5
6
|
import { resolveDataDir } from './paths.js';
|
|
6
7
|
import { auditPersonalOsDirectory } from './ontology-ssot.js';
|
|
7
8
|
import { getOntologyImpact, inferPersonalOs, portableOntology, resolveOntologyVersion } from './ontology.js';
|
|
@@ -16,6 +17,71 @@ const argsSchema = z.object({
|
|
|
16
17
|
ontologyVersion: z.string().optional(),
|
|
17
18
|
asOf: z.string().datetime({ offset: true }).optional()
|
|
18
19
|
});
|
|
20
|
+
const sourceInventorySchema = z.object({
|
|
21
|
+
id: z.string(),
|
|
22
|
+
mode: z.enum(['mcp', 'drive', 'gmail', 'local_folder', 'single_document']),
|
|
23
|
+
status: z.enum(['ready', 'waiting_for_authorization', 'unavailable', 'error', 'unconfirmed']),
|
|
24
|
+
evidencePointer: z.string().optional(),
|
|
25
|
+
permissionScope: z.array(z.string()).optional(),
|
|
26
|
+
detail: z.string().optional()
|
|
27
|
+
}).strict();
|
|
28
|
+
const candidateSchema = z.object({
|
|
29
|
+
kind: z.string(),
|
|
30
|
+
payload: z.record(z.unknown()),
|
|
31
|
+
observationClass: z.enum(['observed', 'inferred']),
|
|
32
|
+
evidenceId: z.string()
|
|
33
|
+
}).strict();
|
|
34
|
+
const reviewActionSchema = z.discriminatedUnion('decision', [
|
|
35
|
+
z.object({ candidateId: z.string(), decision: z.literal('approve'), reason: z.string() }).strict(),
|
|
36
|
+
z.object({ candidateId: z.string(), decision: z.literal('edit'), reason: z.string(), payload: z.record(z.unknown()) }).strict(),
|
|
37
|
+
z.object({ candidateId: z.string(), decision: z.literal('reject'), reason: z.string() }).strict(),
|
|
38
|
+
z.object({ candidateId: z.string(), decision: z.literal('merge'), reason: z.string(), mergeIntoCandidateId: z.string() }).strict()
|
|
39
|
+
]);
|
|
40
|
+
const connectedSchemas = {
|
|
41
|
+
brainbase_onboarding_start: z.object({
|
|
42
|
+
dataDir: z.string().optional(),
|
|
43
|
+
valueTarget: z.string(),
|
|
44
|
+
sources: z.array(sourceInventorySchema)
|
|
45
|
+
}).strict(),
|
|
46
|
+
brainbase_onboarding_get: z.object({ dataDir: z.string().optional(), runId: z.string() }).strict(),
|
|
47
|
+
brainbase_onboarding_ingest: z.object({
|
|
48
|
+
dataDir: z.string().optional(),
|
|
49
|
+
runId: z.string(),
|
|
50
|
+
source: z.object({
|
|
51
|
+
sourceId: z.string(),
|
|
52
|
+
evidencePointer: z.string(),
|
|
53
|
+
contentHash: z.string(),
|
|
54
|
+
permissionSnapshot: z.record(z.unknown()),
|
|
55
|
+
collectionStatus: z.literal('collected')
|
|
56
|
+
}).strict(),
|
|
57
|
+
candidates: z.array(candidateSchema)
|
|
58
|
+
}).strict(),
|
|
59
|
+
brainbase_onboarding_review: z.object({
|
|
60
|
+
dataDir: z.string().optional(),
|
|
61
|
+
runId: z.string(),
|
|
62
|
+
actions: z.array(reviewActionSchema)
|
|
63
|
+
}).strict(),
|
|
64
|
+
brainbase_onboarding_first_value: z.discriminatedUnion('action', [
|
|
65
|
+
z.object({
|
|
66
|
+
dataDir: z.string().optional(),
|
|
67
|
+
runId: z.string(),
|
|
68
|
+
action: z.literal('record'),
|
|
69
|
+
answerHash: z.string(),
|
|
70
|
+
usedCanonicalIds: z.array(z.string()),
|
|
71
|
+
verdict: z.enum(['useful', 'not_useful']).optional(),
|
|
72
|
+
missingContext: z.array(z.string()).optional()
|
|
73
|
+
}).strict(),
|
|
74
|
+
z.object({
|
|
75
|
+
dataDir: z.string().optional(),
|
|
76
|
+
runId: z.string(),
|
|
77
|
+
action: z.literal('review'),
|
|
78
|
+
answerHash: z.string().optional(),
|
|
79
|
+
usedCanonicalIds: z.array(z.string()).optional(),
|
|
80
|
+
verdict: z.enum(['useful', 'not_useful']),
|
|
81
|
+
missingContext: z.array(z.string()).optional()
|
|
82
|
+
}).strict()
|
|
83
|
+
])
|
|
84
|
+
};
|
|
19
85
|
export const toolDefinitions = [
|
|
20
86
|
{
|
|
21
87
|
name: 'get_context',
|
|
@@ -40,7 +106,7 @@ export const toolDefinitions = [
|
|
|
40
106
|
},
|
|
41
107
|
{
|
|
42
108
|
name: 'search',
|
|
43
|
-
description: 'Search canonical local Graph, Personal KG, relationships, and decisions.',
|
|
109
|
+
description: 'Search all canonical local stores: Graph entities, Personal KG, relationships, and decisions. Use this for people and projects.',
|
|
44
110
|
inputSchema: {
|
|
45
111
|
type: 'object',
|
|
46
112
|
required: ['query'],
|
|
@@ -53,7 +119,7 @@ export const toolDefinitions = [
|
|
|
53
119
|
},
|
|
54
120
|
{
|
|
55
121
|
name: 'search_personal_kg',
|
|
56
|
-
description: 'Search owner-local Personal KG only.',
|
|
122
|
+
description: 'Search owner-local Personal KG only. People and projects are Graph entities, so use search for them.',
|
|
57
123
|
inputSchema: {
|
|
58
124
|
type: 'object',
|
|
59
125
|
required: ['query'],
|
|
@@ -74,9 +140,84 @@ export const toolDefinitions = [
|
|
|
74
140
|
}
|
|
75
141
|
}
|
|
76
142
|
},
|
|
143
|
+
{
|
|
144
|
+
name: 'brainbase_onboarding_start',
|
|
145
|
+
description: 'Start a local first-value onboarding run from actually callable sources. The result exposes runId (also id); pass runId to every later onboarding tool.',
|
|
146
|
+
inputSchema: {
|
|
147
|
+
type: 'object', required: ['valueTarget', 'sources'], additionalProperties: false,
|
|
148
|
+
properties: {
|
|
149
|
+
dataDir: { type: 'string', description: 'Optional Personal OS directory.' }, valueTarget: { type: 'string', description: 'One concrete question the first useful answer should resolve.' },
|
|
150
|
+
sources: { type: 'array', maxItems: 50, items: { type: 'object', required: ['id', 'mode', 'status'], additionalProperties: false, properties: {
|
|
151
|
+
id: { type: 'string' }, mode: { enum: ['mcp', 'drive', 'gmail', 'local_folder', 'single_document'] },
|
|
152
|
+
status: { enum: ['ready', 'waiting_for_authorization', 'unavailable', 'error', 'unconfirmed'] }, evidencePointer: { type: 'string' },
|
|
153
|
+
permissionScope: { type: 'array', items: { type: 'string' } }, detail: { type: 'string' }
|
|
154
|
+
} } }
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'brainbase_onboarding_get',
|
|
160
|
+
description: 'Read a connected-world onboarding run without exposing source or answer bodies.',
|
|
161
|
+
inputSchema: { type: 'object', required: ['runId'], additionalProperties: false, properties: { dataDir: { type: 'string' }, runId: { type: 'string' } } }
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: 'brainbase_onboarding_ingest',
|
|
165
|
+
description: 'Ingest one selected source receipt under source plus review candidates. Do not flatten sourceId or receipt fields at the top level.',
|
|
166
|
+
inputSchema: {
|
|
167
|
+
type: 'object', required: ['runId', 'source', 'candidates'], additionalProperties: false,
|
|
168
|
+
properties: {
|
|
169
|
+
dataDir: { type: 'string' }, runId: { type: 'string', description: 'runId returned by brainbase_onboarding_start.' },
|
|
170
|
+
source: { type: 'object', required: ['sourceId', 'evidencePointer', 'contentHash', 'permissionSnapshot', 'collectionStatus'], additionalProperties: false, properties: {
|
|
171
|
+
sourceId: { type: 'string' }, evidencePointer: { type: 'string' }, contentHash: { type: 'string' }, permissionSnapshot: { type: 'object' }, collectionStatus: { const: 'collected' }
|
|
172
|
+
} },
|
|
173
|
+
candidates: { type: 'array', maxItems: 50, items: { type: 'object', required: ['kind', 'payload', 'observationClass', 'evidenceId'], additionalProperties: false, properties: {
|
|
174
|
+
kind: { type: 'string' }, payload: { type: 'object' }, observationClass: { enum: ['observed', 'inferred'] }, evidenceId: { type: 'string' }
|
|
175
|
+
} } }
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: 'brainbase_onboarding_review',
|
|
181
|
+
description: 'Submit review decisions in actions. Inferred candidates cannot be approved or merged; use edit with a human-confirmed payload, or reject.',
|
|
182
|
+
inputSchema: {
|
|
183
|
+
type: 'object', required: ['runId', 'actions'], additionalProperties: false,
|
|
184
|
+
properties: { dataDir: { type: 'string' }, runId: { type: 'string', minLength: 1, maxLength: 200 }, actions: { type: 'array', minItems: 1, maxItems: 50, items: {
|
|
185
|
+
oneOf: [
|
|
186
|
+
{ type: 'object', required: ['candidateId', 'decision', 'reason'], additionalProperties: false, properties: {
|
|
187
|
+
candidateId: { type: 'string', minLength: 1, maxLength: 200 }, decision: { const: 'approve' }, reason: { type: 'string', minLength: 1, maxLength: 500 }
|
|
188
|
+
} },
|
|
189
|
+
{ type: 'object', required: ['candidateId', 'decision', 'reason', 'payload'], additionalProperties: false, properties: {
|
|
190
|
+
candidateId: { type: 'string', minLength: 1, maxLength: 200 }, decision: { const: 'edit' }, reason: { type: 'string', minLength: 1, maxLength: 500 }, payload: { type: 'object' }
|
|
191
|
+
} },
|
|
192
|
+
{ type: 'object', required: ['candidateId', 'decision', 'reason'], additionalProperties: false, properties: {
|
|
193
|
+
candidateId: { type: 'string', minLength: 1, maxLength: 200 }, decision: { const: 'reject' }, reason: { type: 'string', minLength: 1, maxLength: 500 }
|
|
194
|
+
} },
|
|
195
|
+
{ type: 'object', required: ['candidateId', 'decision', 'reason', 'mergeIntoCandidateId'], additionalProperties: false, properties: {
|
|
196
|
+
candidateId: { type: 'string', minLength: 1, maxLength: 200 }, decision: { const: 'merge' }, reason: { type: 'string', minLength: 1, maxLength: 500 }, mergeIntoCandidateId: { type: 'string', minLength: 1, maxLength: 200 }
|
|
197
|
+
} }
|
|
198
|
+
]
|
|
199
|
+
} } }
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: 'brainbase_onboarding_first_value',
|
|
204
|
+
description: 'Finish onboarding in two calls. First choose action=record with an answer hash and promoted canonical IDs. Then choose action=review with useful or not_useful.',
|
|
205
|
+
inputSchema: {
|
|
206
|
+
type: 'object', required: ['runId', 'action'], additionalProperties: false,
|
|
207
|
+
properties: {
|
|
208
|
+
dataDir: { type: 'string', description: 'Optional Personal OS directory.' },
|
|
209
|
+
runId: { type: 'string', minLength: 1, maxLength: 200, description: 'runId returned by the previous onboarding step.' },
|
|
210
|
+
action: { enum: ['record', 'review'], description: 'Use record first. Use review only after the answer receipt has been recorded.' },
|
|
211
|
+
answerHash: { type: 'string', pattern: '^sha256:[a-f0-9]{64}$', description: 'Required for action=record. SHA-256 of the answer body; the body itself is not stored.' },
|
|
212
|
+
usedCanonicalIds: { type: 'array', minItems: 1, maxItems: 50, items: { type: 'string', minLength: 1, maxLength: 200 }, description: 'Required for action=record. Use only promotedCanonicalIds returned by review.' },
|
|
213
|
+
verdict: { enum: ['useful', 'not_useful'], description: 'Required for action=review.' },
|
|
214
|
+
missingContext: { type: 'array', maxItems: 50, items: { type: 'string', minLength: 1, maxLength: 200 }, description: 'Optional short labels for context the answer still lacked.' }
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
},
|
|
77
218
|
{
|
|
78
219
|
name: 'get_ontology',
|
|
79
|
-
description: 'Return the bundled immutable Brainbase Portable Ontology Kernel release.',
|
|
220
|
+
description: 'Return a beginner guide followed by the bundled immutable Brainbase Portable Ontology Kernel release.',
|
|
80
221
|
inputSchema: {
|
|
81
222
|
type: 'object',
|
|
82
223
|
properties: {}
|
|
@@ -117,11 +258,63 @@ export const toolDefinitions = [
|
|
|
117
258
|
}
|
|
118
259
|
];
|
|
119
260
|
export async function callBrainbaseTool(name, rawArgs = {}) {
|
|
261
|
+
if (name in connectedSchemas) {
|
|
262
|
+
return callConnectedOnboardingTool(name, rawArgs);
|
|
263
|
+
}
|
|
120
264
|
const args = argsSchema.parse(rawArgs ?? {});
|
|
121
265
|
const dataDir = resolveDataDir(args.dataDir);
|
|
122
266
|
switch (name) {
|
|
123
267
|
case 'get_ontology':
|
|
124
|
-
return
|
|
268
|
+
return {
|
|
269
|
+
beginnerGuide: {
|
|
270
|
+
startHere: 'まずここだけ読めば大丈夫です。下の業務例から全体像をつかみ、必要なときだけ正式契約を確認してください。',
|
|
271
|
+
oneSentence: 'オントロジーは、仕事の言葉・つながり・守る条件・判断方法・変更履歴を、Brainbaseと人が同じ意味で扱うための約束です。',
|
|
272
|
+
workExample: '例: 「新しい方針が旧方針を置き換えた」と登録すると、Brainbaseは新しい方針を現在有効な判断として扱い、旧方針も履歴として残します。',
|
|
273
|
+
fiveParts: [
|
|
274
|
+
{ id: 'types', name: '種類', question: 'これは何ですか?', example: '人、組織、プロジェクト、関係、意思決定' },
|
|
275
|
+
{ id: 'relations', name: '関係', question: '何とどうつながっていますか?', example: '新しい意思決定が旧意思決定を置き換える' },
|
|
276
|
+
{ id: 'constraints', name: '必須条件', question: '登録前に何が揃っている必要がありますか?', example: '置き換える相手の意思決定が実在する' },
|
|
277
|
+
{ id: 'inference', name: '判断規則', question: '明示した事実から何を判断しますか?', example: '置き換えられた旧意思決定は現在有効ではない' },
|
|
278
|
+
{ id: 'evolution', name: '変更履歴', question: '履歴を失わずに意味をどう変えますか?', example: '監査して新しい版へ移行し、戻し方も残す' }
|
|
279
|
+
],
|
|
280
|
+
changeSafety: {
|
|
281
|
+
check: '変更前は ontology_impact で影響を確認し、audit_ontology で現在の不整合を調べます。',
|
|
282
|
+
recover: '誤った定義は履歴を消さず、新しい版で訂正し、移行と戻し方を記録します。'
|
|
283
|
+
},
|
|
284
|
+
unsafeShortcuts: [
|
|
285
|
+
{
|
|
286
|
+
request: '旧方針や変更履歴を削除して最新版だけにする',
|
|
287
|
+
handling: 'reject_and_explain',
|
|
288
|
+
safeAlternative: '履歴は削除せず、新しい版を作り、supersedes と有効日を記録して置き換えます。'
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
request: '影響確認や監査を完了扱いにして先へ進む',
|
|
292
|
+
handling: 'reject_and_explain',
|
|
293
|
+
safeAlternative: 'ontology_impact と audit_ontology の実行結果を確認してから完了とします。'
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
request: '必須項目を空欄のまま自動で補って登録する',
|
|
297
|
+
handling: 'reject_and_explain',
|
|
298
|
+
safeAlternative: '不足項目を明示し、根拠を確認できるまで登録せず候補として残します。'
|
|
299
|
+
}
|
|
300
|
+
],
|
|
301
|
+
toolChooser: [
|
|
302
|
+
{ goal: '変更の影響を知りたい', tool: 'ontology_impact', when: '定義や関係を変える前に実行します。' },
|
|
303
|
+
{ goal: '現在の不整合を調べたい', tool: 'audit_ontology', when: '変更前の現状確認と、変更後の再確認に使います。' },
|
|
304
|
+
{ goal: '現在有効な判断を知りたい', tool: 'infer_decisions', when: '旧判断を除き、今使う判断を確かめるときに使います。' }
|
|
305
|
+
],
|
|
306
|
+
changeChecklist: [
|
|
307
|
+
'変更前: ontology_impact で影響を確認する。',
|
|
308
|
+
'変更前: audit_ontology で現在の不整合を確認する。',
|
|
309
|
+
'変更時: 旧版を消さず、新版に supersedes と有効日を記録する。',
|
|
310
|
+
'変更後: audit_ontology の実行結果を確認し、未実行なら完了扱いにしない。',
|
|
311
|
+
'問題時: 履歴を残したまま訂正版と戻し方を記録する。'
|
|
312
|
+
],
|
|
313
|
+
detailsNotice: '以下の正式契約は、実装・監査・詳しい確認が必要なときに読みます。',
|
|
314
|
+
suggestedNextTools: ['audit_ontology', 'infer_decisions', 'ontology_impact']
|
|
315
|
+
},
|
|
316
|
+
...portableOntology
|
|
317
|
+
};
|
|
125
318
|
case 'audit_ontology':
|
|
126
319
|
return auditPersonalOsDirectory(dataDir, { ontologyVersion: resolveOntologyVersion(args.ontologyVersion) });
|
|
127
320
|
case 'ontology_impact':
|
|
@@ -154,6 +347,143 @@ export async function callBrainbaseTool(name, rawArgs = {}) {
|
|
|
154
347
|
throw new Error(`Unknown tool: ${name}`);
|
|
155
348
|
}
|
|
156
349
|
}
|
|
350
|
+
async function callConnectedOnboardingTool(name, rawArgs) {
|
|
351
|
+
const schema = connectedSchemas[name];
|
|
352
|
+
const args = schema.parse(rawArgs ?? {});
|
|
353
|
+
const runtime = new ConnectedOnboardingRuntime(resolveDataDir(args.dataDir));
|
|
354
|
+
switch (name) {
|
|
355
|
+
case 'brainbase_onboarding_start':
|
|
356
|
+
return runtime.start(args)
|
|
357
|
+
.then(withOnboardingGuidance);
|
|
358
|
+
case 'brainbase_onboarding_get':
|
|
359
|
+
return runtime.get(args.runId).then(withOnboardingGuidance);
|
|
360
|
+
case 'brainbase_onboarding_ingest': {
|
|
361
|
+
const { runId, dataDir: _dataDir, ...input } = args;
|
|
362
|
+
return runtime.ingest(runId, input).then(withOnboardingGuidance);
|
|
363
|
+
}
|
|
364
|
+
case 'brainbase_onboarding_review':
|
|
365
|
+
return runtime.review(args.runId, args.actions).then(withOnboardingGuidance);
|
|
366
|
+
case 'brainbase_onboarding_first_value': {
|
|
367
|
+
const input = args.action === 'record'
|
|
368
|
+
? {
|
|
369
|
+
action: 'record',
|
|
370
|
+
answerHash: args.answerHash,
|
|
371
|
+
usedCanonicalIds: args.usedCanonicalIds,
|
|
372
|
+
missingContext: args.missingContext
|
|
373
|
+
}
|
|
374
|
+
: {
|
|
375
|
+
action: 'review',
|
|
376
|
+
verdict: args.verdict,
|
|
377
|
+
missingContext: args.missingContext
|
|
378
|
+
};
|
|
379
|
+
return runtime.firstValue(args.runId, input).then(withOnboardingGuidance);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function withOnboardingGuidance(run) {
|
|
384
|
+
const pendingCandidateIds = run.candidates
|
|
385
|
+
.filter((candidate) => candidate.reviewStatus === 'pending')
|
|
386
|
+
.map((candidate) => candidate.id);
|
|
387
|
+
const nextAction = (() => {
|
|
388
|
+
switch (run.state) {
|
|
389
|
+
case 'initialized':
|
|
390
|
+
return onboardingAction('brainbase_onboarding_start', '利用できる情報源を準備する', '利用を許可するか、読み取れる情報源を追加して、新しいオンボーディングを開始します。', [], '新しい実行を作成します。既存データは変更しません。', true, '準備できない場合は開始せず、情報源の設定に戻れます。');
|
|
391
|
+
case 'source_ready':
|
|
392
|
+
return onboardingAction('brainbase_onboarding_ingest', '準備できた情報源を取り込む', '選択済みの情報源から、証拠の記録と確認候補を取り込みます。', run.selectedSourceIds, '確認待ちの候補を作ります。まだ正式な情報にはなりません。', true, '取り込み後も、候補を却下すれば正式な情報には反映されません。');
|
|
393
|
+
case 'candidates_ready':
|
|
394
|
+
return onboardingAction('brainbase_onboarding_review', '候補を確認する', 'すべての候補を確認します。推測された候補は、人が確認した内容へ edit するか、reject で却下してください。', pendingCandidateIds, '確認した候補だけを正式な情報として登録します。', true, '確信がなければ却下できます。誤って登録した場合も履歴を残して訂正できます。');
|
|
395
|
+
case 'promotion_reviewed':
|
|
396
|
+
return onboardingAction('brainbase_onboarding_first_value', '最初の回答を記録する', 'action=record と回答の answerHash、登録済みIDを使って、回答の記録だけを保存します。', run.promotedCanonicalIds, '回答本文ではなくハッシュと使用したIDを記録します。', true, '回答本文は保存されません。記録後に役立ったかを確認できます。');
|
|
397
|
+
case 'first_value_ready':
|
|
398
|
+
return onboardingAction('brainbase_onboarding_first_value', '回答が役立ったか評価する', 'action=review と verdict=useful または not_useful を使って評価します。', [], '最初の回答に対する評価を記録し、オンボーディングを完了します。', true, '役立たなかった場合は not_useful と不足情報を記録できます。');
|
|
399
|
+
case 'first_value_answer_reviewed':
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
})();
|
|
403
|
+
const baseGuide = onboardingGuide(run.state);
|
|
404
|
+
const safetyBoundaries = {
|
|
405
|
+
mode: 'mandatory',
|
|
406
|
+
review: '候補の全件自動承認や確認の省略は行いません。候補ごとに根拠を確認し、承認・編集・却下を選びます。',
|
|
407
|
+
resume: `中断後は同じ runId ${run.id} を取得し、表示された次の操作だけを続けます。`,
|
|
408
|
+
completion: '完了済みの操作は再実行しません。現在の状態と残りの操作を確認してから続けます。'
|
|
409
|
+
};
|
|
410
|
+
const guardedNextAction = nextAction === null ? null : {
|
|
411
|
+
...nextAction,
|
|
412
|
+
inputHelp: onboardingInputHelp(run),
|
|
413
|
+
confirmation: {
|
|
414
|
+
...nextAction.confirmation,
|
|
415
|
+
cannotSkip: '確認の省略や全件自動承認はできません。必要なIDごとに内容と根拠を確認してください。',
|
|
416
|
+
resumeRule: `同じ runId ${run.id} で現在状態を取得し、完了済みの操作は繰り返さず、この操作だけを実行します。`
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
const guide = {
|
|
420
|
+
...baseGuide,
|
|
421
|
+
plainText: guardedNextAction === null
|
|
422
|
+
? `${baseGuide.current} 残りの操作はありません。runIdは${run.id}です。完了済み操作は繰り返しません。`
|
|
423
|
+
: `${baseGuide.current} 次は「${guardedNextAction.label}」だけを行います。runIdは${run.id}です。${baseGuide.remaining}`
|
|
424
|
+
};
|
|
425
|
+
return { guide, nextAction: guardedNextAction, safetyBoundaries, ...run, runId: run.id };
|
|
426
|
+
}
|
|
427
|
+
function onboardingInputHelp(run) {
|
|
428
|
+
switch (run.state) {
|
|
429
|
+
case 'initialized':
|
|
430
|
+
return [{ field: 'sources', meaning: '利用を許可する情報源', source: '準備済みまたは許可待ちの情報源一覧から選びます。' }];
|
|
431
|
+
case 'source_ready':
|
|
432
|
+
return [{ field: 'sourceId', meaning: '今回取り込む情報源', source: 'nextAction.requiredIds に表示されたIDを使います。' }];
|
|
433
|
+
case 'candidates_ready':
|
|
434
|
+
return [
|
|
435
|
+
{ field: 'candidateId', meaning: '確認する候補', source: 'nextAction.requiredIds に表示されたIDを一件ずつ使います。' },
|
|
436
|
+
{ field: 'decision', meaning: '確認結果', source: '内容を確認して edit、確信がなければ reject を選びます。' }
|
|
437
|
+
];
|
|
438
|
+
case 'promotion_reviewed':
|
|
439
|
+
return [
|
|
440
|
+
{ field: 'answerHash', meaning: '回答本文を保存せず同じ回答を識別する値', source: '直前に作った回答からエージェントが生成し、利用者は対象回答だけ確認します。' },
|
|
441
|
+
{ field: 'usedCanonicalIds', meaning: '回答で使った正式情報', source: '直前の promotedCanonicalIds から、実際に回答で使ったIDを選びます。' }
|
|
442
|
+
];
|
|
443
|
+
case 'first_value_ready':
|
|
444
|
+
return [{ field: 'verdict', meaning: '回答が役立ったか', source: '利用者が useful または not_useful を選びます。' }];
|
|
445
|
+
case 'first_value_answer_reviewed':
|
|
446
|
+
return [];
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
function onboardingAction(tool, label, instruction, requiredIds, changes, reversible, recovery) {
|
|
450
|
+
return { tool, label, instruction, requiredIds, confirmation: { changes, reversible, recovery } };
|
|
451
|
+
}
|
|
452
|
+
function onboardingGuide(state) {
|
|
453
|
+
const guides = {
|
|
454
|
+
initialized: {
|
|
455
|
+
current: '利用できる情報源を準備する段階です。',
|
|
456
|
+
completed: [],
|
|
457
|
+
remaining: '情報源の準備、取り込み、候補の確認、最初の回答の評価が残っています。'
|
|
458
|
+
},
|
|
459
|
+
source_ready: {
|
|
460
|
+
current: '利用する情報源を選びました。',
|
|
461
|
+
completed: ['情報源の選択'],
|
|
462
|
+
remaining: '情報の取り込み、候補の確認、最初の回答の評価が残っています。'
|
|
463
|
+
},
|
|
464
|
+
candidates_ready: {
|
|
465
|
+
current: '取り込んだ候補を確認する段階です。',
|
|
466
|
+
completed: ['情報源の選択', '情報の取り込み'],
|
|
467
|
+
remaining: '候補の確認、最初の回答の記録と評価が残っています。'
|
|
468
|
+
},
|
|
469
|
+
promotion_reviewed: {
|
|
470
|
+
current: '確認済みの候補を正式な情報として登録しました。',
|
|
471
|
+
completed: ['情報源の選択', '情報の取り込み', '候補の確認'],
|
|
472
|
+
remaining: '最初の回答の記録と評価が残っています。'
|
|
473
|
+
},
|
|
474
|
+
first_value_ready: {
|
|
475
|
+
current: '最初の回答を記録しました。',
|
|
476
|
+
completed: ['情報源の選択', '情報の取り込み', '候補の確認', '最初の回答の記録'],
|
|
477
|
+
remaining: '回答が役立ったかの評価が残っています。'
|
|
478
|
+
},
|
|
479
|
+
first_value_answer_reviewed: {
|
|
480
|
+
current: '最初の回答の評価まで完了しました。',
|
|
481
|
+
completed: ['情報源の選択', '情報の取り込み', '候補の確認', '最初の回答の記録', '回答の評価'],
|
|
482
|
+
remaining: 'ありません。'
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
return guides[state];
|
|
486
|
+
}
|
|
157
487
|
export function createServer() {
|
|
158
488
|
const server = new Server({
|
|
159
489
|
name: 'brainbase-mcp',
|
package/dist/skills.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type SkillTarget = 'codex' | 'claude' | 'portable';
|
|
2
|
-
export type BrainbaseSkillId = 'brainbase-personal-onboarding' | 'brainbase-source-import' | 'brainbase-candidate-review' | 'brainbase-daily-routines';
|
|
2
|
+
export type BrainbaseSkillId = 'brainbase-personal-onboarding' | 'brainbase-connected-world-onboarding' | 'brainbase-source-import' | 'brainbase-candidate-review' | 'brainbase-daily-routines';
|
|
3
3
|
export interface BrainbaseSkillDefinition {
|
|
4
4
|
id: BrainbaseSkillId;
|
|
5
5
|
title: string;
|
package/dist/skills.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export const ALL_BRAINBASE_SKILLS = [
|
|
2
2
|
'brainbase-personal-onboarding',
|
|
3
|
+
'brainbase-connected-world-onboarding',
|
|
3
4
|
'brainbase-source-import',
|
|
4
5
|
'brainbase-candidate-review',
|
|
5
6
|
'brainbase-daily-routines'
|
|
@@ -29,6 +30,7 @@ const SKILL_DEFINITIONS = {
|
|
|
29
30
|
'5. `brainbase onboard:candidates --write`、またはsource import/extractの流れでレビュー候補を作る。',
|
|
30
31
|
'6. ユーザーが明示的に承認した事実だけを `brainbase onboard:seed`、`brainbase onboard:projects --write`、または `brainbase onboard:apply --write` でcanonical SSOTへ昇格する。',
|
|
31
32
|
'7. `brainbase onboard:install --target codex|claude|codecode --dry-run` でMCP設定スニペットを生成し、ユーザーに確認してもらう。',
|
|
33
|
+
'8. 接続済みソースから最初の価値まで進める場合は `brainbase-connected-world-onboarding` Skillを使う。',
|
|
32
34
|
'',
|
|
33
35
|
'## 安全ルール',
|
|
34
36
|
'',
|
|
@@ -38,6 +40,36 @@ const SKILL_DEFINITIONS = {
|
|
|
38
40
|
'- canonical contextは、ユーザー確認後の `graph.json`、`relationships.json`、`personal-kg.jsonl`、`decisions.jsonl` だけから作る。'
|
|
39
41
|
].join('\n')
|
|
40
42
|
},
|
|
43
|
+
'brainbase-connected-world-onboarding': {
|
|
44
|
+
id: 'brainbase-connected-world-onboarding',
|
|
45
|
+
title: 'Brainbase接続済み世界オンボーディング',
|
|
46
|
+
description: '実際に呼び出せる仕事ソースを棚卸しし、承認済み事実だけで最初の価値まで進める。',
|
|
47
|
+
body: [
|
|
48
|
+
'## 目的',
|
|
49
|
+
'',
|
|
50
|
+
'既に接続できる仕事ソースから、最小範囲の証拠を人間レビューへつなぎ、10分以内に最初の価値を評価する。',
|
|
51
|
+
'',
|
|
52
|
+
'## 手順',
|
|
53
|
+
'',
|
|
54
|
+
'1. 最初に「Brainbaseで最初に何が分かると価値があるか」を一つ聞く。',
|
|
55
|
+
'2. 自分が実際に呼び出せるMCP、Drive、Gmailを確認する。ローカルはユーザーが明示した限定フォルダ、fallbackは明示された単一ドキュメントだけを対象にする。',
|
|
56
|
+
'3. 各sourceを `ready`、`waiting_for_authorization`、`unavailable`、`error`、`unconfirmed` に分ける。認証失敗、timeout、未確認を0件やreadyにしない。',
|
|
57
|
+
'4. 最初の価値とsource inventoryを `brainbase_onboarding_start` に渡し、返された `selectedSourceIds` だけを使う。',
|
|
58
|
+
'5. selected sourceをmetadata-firstで最小範囲だけ取得し、本文ではなくpointer、SHA-256 hash、permission snapshotと短い構造化候補を `brainbase_onboarding_ingest` に渡す。',
|
|
59
|
+
'6. `brainbase_onboarding_get` の候補を、要約・evidence ID・observed/inferred区分付きでユーザーへ示す。',
|
|
60
|
+
'7. ユーザーの判断を `brainbase_onboarding_review` の `approve`、`edit`、`reject`、`merge` とreasonで記録する。inferred候補はapproveせず、確認できた内容へeditするかrejectする。',
|
|
61
|
+
'8. 昇格済みcanonical IDだけで最初の回答を作る。回答本文を渡さず、hashと使用IDを `brainbase_onboarding_first_value` の `record` で記録する。',
|
|
62
|
+
'9. ユーザーへ役立ったかを聞き、`useful` または `not_useful` と不足文脈を同toolの `review` で記録する。',
|
|
63
|
+
'',
|
|
64
|
+
'## 安全ルール',
|
|
65
|
+
'',
|
|
66
|
+
`- ${SECRET_RULE}`,
|
|
67
|
+
'- 全Drive、全mailbox、home directory全体を走査しない。selected sourceのpermission scopeを越えない。',
|
|
68
|
+
'- source本文と回答本文をBrainbase onboarding ledgerへ保存しない。',
|
|
69
|
+
'- 候補はcanonical memoryではない。人間が明示的にreviewしたobserved factだけを昇格する。',
|
|
70
|
+
'- connectorが使えない時は状態をそのまま報告し、別sourceまたは明示された単一ドキュメントを提案する。'
|
|
71
|
+
].join('\n')
|
|
72
|
+
},
|
|
41
73
|
'brainbase-source-import': {
|
|
42
74
|
id: 'brainbase-source-import',
|
|
43
75
|
title: 'Brainbaseソース取り込み',
|
package/dist/ssot.d.ts
CHANGED
|
@@ -2,3 +2,12 @@ import type { PersonalOs } from './types.js';
|
|
|
2
2
|
export declare function initializePersonalOs(dataDir: string): Promise<void>;
|
|
3
3
|
export declare function loadPersonalOs(dataDir: string): Promise<PersonalOs>;
|
|
4
4
|
export declare function mutatePersonalOs(dataDir: string, mutator: (current: PersonalOs) => PersonalOs | Promise<PersonalOs>): Promise<PersonalOs>;
|
|
5
|
+
export declare function mutatePersonalOsWithSidecar<T>(dataDir: string, sidecarPath: string, mutator: (current: PersonalOs) => {
|
|
6
|
+
next: PersonalOs;
|
|
7
|
+
sidecarContent: string;
|
|
8
|
+
result: T;
|
|
9
|
+
} | Promise<{
|
|
10
|
+
next: PersonalOs;
|
|
11
|
+
sidecarContent: string;
|
|
12
|
+
result: T;
|
|
13
|
+
}>): Promise<T>;
|
package/dist/ssot.js
CHANGED
|
@@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { constants } from 'node:fs';
|
|
3
3
|
import { access, copyFile, mkdir, readFile, readdir, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { hostname } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
5
|
+
import { dirname, isAbsolute, join, relative, resolve, win32 } from 'node:path';
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import { assertOntologyValid } from './ontology.js';
|
|
8
8
|
import { emptyGraph, emptyRelationships, schemaTemplates } from './templates.js';
|
|
@@ -99,6 +99,19 @@ export async function mutatePersonalOs(dataDir, mutator) {
|
|
|
99
99
|
return normalized;
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
|
+
export async function mutatePersonalOsWithSidecar(dataDir, sidecarPath, mutator) {
|
|
103
|
+
assertSafeSidecarPath(sidecarPath);
|
|
104
|
+
return withSsotLock(dataDir, async () => {
|
|
105
|
+
await recoverTransactions(dataDir);
|
|
106
|
+
assertCompleteCanonicalSet(dataDir, await canonicalPresence(dataDir));
|
|
107
|
+
const current = await loadPersonalOsUnlocked(dataDir);
|
|
108
|
+
const mutation = await mutator(current);
|
|
109
|
+
const normalized = { ...mutation.next, dataDir, sourceCount: current.sourceCount };
|
|
110
|
+
validateAggregate(normalized);
|
|
111
|
+
await commitAggregate(dataDir, normalized, 'mutation', [{ relativePath: sidecarPath, content: mutation.sidecarContent }]);
|
|
112
|
+
return mutation.result;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
102
115
|
async function loadPersonalOsUnlocked(dataDir) {
|
|
103
116
|
const graph = graphSchema.parse(await readJson(join(dataDir, 'graph.json')));
|
|
104
117
|
const relationships = relationshipsSchema.parse(await readJson(join(dataDir, 'relationships.json')));
|
|
@@ -107,7 +120,7 @@ async function loadPersonalOsUnlocked(dataDir) {
|
|
|
107
120
|
const sourceCount = await countSources(dataDir);
|
|
108
121
|
return { dataDir, graph, personalKg, relationships, decisions, sourceCount };
|
|
109
122
|
}
|
|
110
|
-
async function commitAggregate(dataDir, next, mode) {
|
|
123
|
+
async function commitAggregate(dataDir, next, mode, sidecars = []) {
|
|
111
124
|
validateAggregate(next);
|
|
112
125
|
const id = randomUUID();
|
|
113
126
|
const stagingDir = join(dataDir, `${stagingPrefix}${id}`);
|
|
@@ -116,16 +129,25 @@ async function commitAggregate(dataDir, next, mode) {
|
|
|
116
129
|
const previousDir = join(stagingDir, 'previous');
|
|
117
130
|
await mkdir(nextDir, { recursive: true });
|
|
118
131
|
await writeAggregate(nextDir, next);
|
|
132
|
+
for (const sidecar of sidecars) {
|
|
133
|
+
assertSafeSidecarPath(sidecar.relativePath);
|
|
134
|
+
await mkdir(dirname(join(nextDir, sidecar.relativePath)), { recursive: true });
|
|
135
|
+
await writeFile(join(nextDir, sidecar.relativePath), sidecar.content, { mode: 0o600 });
|
|
136
|
+
}
|
|
119
137
|
if (mode === 'mutation') {
|
|
120
138
|
await mkdir(previousDir, { recursive: true });
|
|
121
139
|
await copyCanonicalSet(dataDir, previousDir);
|
|
140
|
+
for (const sidecar of sidecars) {
|
|
141
|
+
await mkdir(dirname(join(previousDir, sidecar.relativePath)), { recursive: true });
|
|
142
|
+
await copyFile(join(dataDir, sidecar.relativePath), join(previousDir, sidecar.relativePath));
|
|
143
|
+
}
|
|
122
144
|
}
|
|
123
|
-
const metadata = { version: 1, mode };
|
|
145
|
+
const metadata = { version: 1, mode, ...(sidecars.length > 0 ? { sidecarFiles: sidecars.map((item) => item.relativePath) } : {}) };
|
|
124
146
|
await writeFile(join(stagingDir, 'transaction.json'), `${JSON.stringify(metadata, null, 2)}\n`);
|
|
125
147
|
await writeFile(join(stagingDir, 'PREPARED'), '');
|
|
126
148
|
await rename(stagingDir, transactionDir);
|
|
127
149
|
try {
|
|
128
|
-
await publishCanonicalSet(dataDir, join(transactionDir, 'next'), id, true);
|
|
150
|
+
await publishCanonicalSet(dataDir, join(transactionDir, 'next'), id, true, metadata.sidecarFiles ?? []);
|
|
129
151
|
await writeFile(join(transactionDir, 'COMMITTED'), '');
|
|
130
152
|
}
|
|
131
153
|
catch (error) {
|
|
@@ -168,10 +190,10 @@ async function recoverTransaction(dataDir, transactionDir) {
|
|
|
168
190
|
if (process.env.BRAINBASE_SSOT_FAIL_RECOVERY === '1') {
|
|
169
191
|
throw new Error(`Injected SSOT transaction recovery failure for ${transactionDir}`);
|
|
170
192
|
}
|
|
171
|
-
await publishCanonicalSet(dataDir, retainedDir, `recovery-${randomUUID()}`, false);
|
|
193
|
+
await publishCanonicalSet(dataDir, retainedDir, `recovery-${randomUUID()}`, false, metadata.sidecarFiles ?? []);
|
|
172
194
|
await rm(transactionDir, { recursive: true, force: true });
|
|
173
195
|
}
|
|
174
|
-
async function publishCanonicalSet(dataDir, retainedDir, token, allowInjectedFailure) {
|
|
196
|
+
async function publishCanonicalSet(dataDir, retainedDir, token, allowInjectedFailure, sidecarFiles = []) {
|
|
175
197
|
let published = 0;
|
|
176
198
|
const failAfter = Number.parseInt(process.env.BRAINBASE_SSOT_FAIL_AFTER_PUBLISH ?? '', 10);
|
|
177
199
|
const pauseAfterPublishMs = parsePositiveInteger(process.env.BRAINBASE_SSOT_PAUSE_AFTER_PUBLISH_MS, 0);
|
|
@@ -187,6 +209,18 @@ async function publishCanonicalSet(dataDir, retainedDir, token, allowInjectedFai
|
|
|
187
209
|
throw new Error(`Injected SSOT publish failure after ${published} file(s)`);
|
|
188
210
|
}
|
|
189
211
|
}
|
|
212
|
+
for (const relativePath of sidecarFiles) {
|
|
213
|
+
assertSafeSidecarPath(relativePath);
|
|
214
|
+
const target = join(dataDir, relativePath);
|
|
215
|
+
await mkdir(dirname(target), { recursive: true });
|
|
216
|
+
const temporary = `${target}-${token}.tmp`;
|
|
217
|
+
await copyFile(join(retainedDir, relativePath), temporary);
|
|
218
|
+
await rename(temporary, target);
|
|
219
|
+
published += 1;
|
|
220
|
+
if (allowInjectedFailure && Number.isFinite(failAfter) && published === failAfter) {
|
|
221
|
+
throw new Error(`Injected SSOT publish failure after ${published} file(s)`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
190
224
|
}
|
|
191
225
|
async function writeAggregate(targetDir, os) {
|
|
192
226
|
await writeFile(join(targetDir, 'graph.json'), `${JSON.stringify(os.graph, null, 2)}\n`);
|
|
@@ -318,8 +352,34 @@ async function readTransactionMetadata(transactionDir) {
|
|
|
318
352
|
if (value.version !== 1 || (value.mode !== 'initialization' && value.mode !== 'mutation')) {
|
|
319
353
|
throw new Error(`Invalid registered SSOT transaction metadata in ${transactionDir}`);
|
|
320
354
|
}
|
|
355
|
+
if (value.sidecarFiles !== undefined && (!Array.isArray(value.sidecarFiles) || value.sidecarFiles.some((path) => typeof path !== 'string'))) {
|
|
356
|
+
throw new Error(`Invalid registered SSOT transaction sidecars in ${transactionDir}`);
|
|
357
|
+
}
|
|
358
|
+
for (const path of value.sidecarFiles ?? [])
|
|
359
|
+
assertSafeSidecarPath(path);
|
|
321
360
|
return value;
|
|
322
361
|
}
|
|
362
|
+
function assertSafeSidecarPath(relativePath) {
|
|
363
|
+
const resolvedRoot = resolve('.');
|
|
364
|
+
const resolvedPath = resolve(resolvedRoot, relativePath);
|
|
365
|
+
const containment = relative(resolvedRoot, resolvedPath);
|
|
366
|
+
const caseFoldedPath = relativePath.toLocaleLowerCase('en-US');
|
|
367
|
+
const topLevel = caseFoldedPath.split('/')[0];
|
|
368
|
+
const collidesWithManagedPath = canonicalFiles.includes(caseFoldedPath)
|
|
369
|
+
|| topLevel === lockName.toLocaleLowerCase('en-US')
|
|
370
|
+
|| topLevel.startsWith(stagingPrefix.toLocaleLowerCase('en-US'))
|
|
371
|
+
|| topLevel.startsWith(transactionPrefix.toLocaleLowerCase('en-US'));
|
|
372
|
+
if (!relativePath
|
|
373
|
+
|| relativePath.includes('\\')
|
|
374
|
+
|| isAbsolute(relativePath)
|
|
375
|
+
|| win32.isAbsolute(relativePath)
|
|
376
|
+
|| containment === '..'
|
|
377
|
+
|| containment.startsWith(`..${process.platform === 'win32' ? '\\' : '/'}`)
|
|
378
|
+
|| relativePath.split('/').some((segment) => segment === '' || segment === '.' || segment === '..')
|
|
379
|
+
|| collidesWithManagedPath) {
|
|
380
|
+
throw new Error(`Unsafe SSOT transaction sidecar path: ${relativePath}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
323
383
|
async function exists(path) {
|
|
324
384
|
try {
|
|
325
385
|
await access(path, constants.F_OK);
|
package/dist/tools.js
CHANGED
|
@@ -107,14 +107,23 @@ export function onboardingStatus(os) {
|
|
|
107
107
|
firstValueReady: valueDemoReady
|
|
108
108
|
});
|
|
109
109
|
return {
|
|
110
|
-
connected: true,
|
|
111
110
|
dataDir: os.dataDir,
|
|
111
|
+
localBackend: {
|
|
112
|
+
connected: true,
|
|
113
|
+
backend: 'local'
|
|
114
|
+
},
|
|
115
|
+
agentMcp: {
|
|
116
|
+
status: 'not_verified',
|
|
117
|
+
verification: 'MCP設定を反映した新しいエージェントセッションで get_context / search を確認してください。'
|
|
118
|
+
},
|
|
119
|
+
operationallyReady: false,
|
|
112
120
|
seeded,
|
|
113
121
|
missing,
|
|
114
122
|
valueDemo: {
|
|
123
|
+
scope: 'local_cli_sample',
|
|
115
124
|
ready: valueDemoReady,
|
|
116
125
|
missing,
|
|
117
|
-
command:
|
|
126
|
+
command: `brainbase onboard:demo --dir ${shellArg(os.dataDir)} --scenario "<real request>"`,
|
|
118
127
|
completionSignal: valueDemoReady ? 'first_value_demo_ready' : 'needs_seed'
|
|
119
128
|
},
|
|
120
129
|
operationalization,
|
|
@@ -124,10 +133,12 @@ export function onboardingStatus(os) {
|
|
|
124
133
|
relationships: os.relationships.relationships.length,
|
|
125
134
|
decisions: os.decisions.length,
|
|
126
135
|
rawSources: os.sourceCount
|
|
127
|
-
}
|
|
128
|
-
backend: 'local'
|
|
136
|
+
}
|
|
129
137
|
};
|
|
130
138
|
}
|
|
139
|
+
function shellArg(value) {
|
|
140
|
+
return /^[A-Za-z0-9_./:@%+=,-]+$/.test(value) ? value : JSON.stringify(value);
|
|
141
|
+
}
|
|
131
142
|
function rank(results, limit) {
|
|
132
143
|
return results
|
|
133
144
|
.filter((result) => result.score > 0)
|