@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 CHANGED
@@ -1,22 +1,24 @@
1
- # Brainbase Personal Onboarding Kit
1
+ # Brainbase 個人オンボーディングキット
2
2
 
3
- Brainbase is a local-first MCP server for handing your personal source of truth to AI coding tools.
3
+ Brainbaseは、自分が承認した仕事の前提をCodex、Claude Code、CodeCodeへ渡すための、ローカル優先のMCPサーバーです。
4
4
 
5
- The v1 value is narrow by design: create a canonical local SSOT for yourself, your work, relationships, and decisions, then expose it through MCP tools that Codex, Claude, and CodeCode can call.
5
+ 最初の目標は情報源をすべて接続することではありません。自分、仕事、関係者、判断基準の最小文脈を保存し、10分以内に「同じ前提を説明し直さず役立つ出力」を確認することです。
6
6
 
7
- Ontology 1.0.0 adds a portable semantic contract on top of those local files. It defines types, relation vocabulary, validation constraints, deterministic decision inference, and version-evolution guidance without requiring a hosted Brainbase service.
7
+ Ontology 1.0.0は、ローカルファイルへ持ち運べる意味契約を追加します。ホスト型Brainbaseを必要とせず、型、関係語彙、検証制約、決定論的な判断推論、バージョン移行を定義します。
8
8
 
9
- This repository does not include the internal Brainbase UI, session runtime, xterm transport, workflow mission control, social operations, hosted backend, Infisical setup, or Unson internal data. Those belong in the internal `brainbase-unson` system.
9
+ このリポジトリに、社内BrainbaseのUI、セッション実行基盤、xterm転送、ワークフロー管制、SNS運用、ホスト型バックエンド、Infisical設定、雲孫の社内データは含みません。それらは社内版`brainbase-unson`の範囲です。
10
10
 
11
- ## Manual
11
+ ## マニュアル
12
12
 
13
13
  Read the public onboarding manual at [brainbase.pages.dev](https://brainbase.pages.dev/). It guides users through five phases: choose one real use case, register approved work context, prove the first value, add only necessary sources, and operationalize Skills, routines, and MCP.
14
14
 
15
+ For the shortest safe path, open [10分で試す](https://brainbase.pages.dev/guide/quick-start). It keeps the first prompt, MCP setup, optional Judgment Host setup, verification, and interruption recovery in one resumable checklist.
16
+
15
17
  The manual is the best starting point for first-time users. It explains Brainbase concepts, the first onboarding flow, MCP registration, project context setup, source onboarding, daily routines, and CLI reference.
16
18
 
17
- ## Agent-assisted Onboarding
19
+ ## エージェントと始める
18
20
 
19
- Brainbase is designed to be adopted from Codex, Claude Code, or CodeCode. The first onboarding goal is a useful answer from your own context, not connector setup.
21
+ BrainbaseはCodex、Claude Code、CodeCodeから導入できます。最初に目指すのは接続設定ではなく、自分の文脈を使った役立つ出力です。
20
22
 
21
23
  ```bash
22
24
  npm install
@@ -24,9 +26,19 @@ npm run build
24
26
  npm run onboard:start -- --target codex
25
27
  ```
26
28
 
27
- `onboard:start` is the Japanese first-run entrypoint for agent-assisted onboarding. It creates the minimum Personal OS directory, but it does not save self, project, relationship, decision, mail, calendar, drive, or task facts until the user approves them. It asks Codex or Claude Code what context you do not want to explain repeatedly, then shows the first prompt to try, the expected value, the minimum seed command, `onboard:demo`, project registration, optional source diagnosis, candidate review, MCP install, and `doctor`. The demo command appears before source diagnosis.
29
+ `onboard:start`は日本語の初回導入コマンドです。最小ディレクトリだけを作り、本人、プロジェクト、関係者、判断、メール、カレンダー、ドライブ、タスクの事実は、利用者が承認するまで保存しません。通常表示は次の一手だけに絞り、全項目は`--details`で確認できます。
30
+
31
+ 公開CLIをインストール済みなら、次の3ステップです。
32
+
33
+ ```bash
34
+ brainbase onboard:start --target codex
35
+ # 表示された onboard:seed を確認して実行
36
+ brainbase onboard:demo --scenario "実際に試す依頼"
37
+ ```
38
+
39
+ リポジトリをcloneした場合は、同じ流れを`npm run onboard:start -- --target codex`、表示された`npm run`コマンド、`npm run onboard:demo -- --scenario "実際に試す依頼"`で進めます。
28
40
 
29
- If the user says "I want to onboard Brainbase", the agent should run this flow instead of returning a checklist. The expected sequence is: build if needed, run `onboard:start`, ask for the smallest context the user wants Brainbase to remember, seed only approved facts, then run `onboard:demo` with a real request. The agent must show the prompt, sample result, what the user no longer had to explain, and the still-unfinished operationalization work. `ready: true`, `first_value_demo_ready`, generated skills, generated routines, and `onboard:install --dry-run` are not completion signals by themselves.
41
+ 利用者がBrainbaseの導入を依頼したら、エージェントはチェックリストを返すだけでなく、この公開CLIを実行します。最小文脈を確認し、承認された事実だけを`onboard:seed`で保存し、現実の依頼で`onboard:demo`を実行します。プロンプト、出力、説明し直さずに済んだ前提、まだ終わっていない運用化を分けて示します。`ready: true`、`first_value_demo_ready`、Skillsやルーティンの生成、`onboard:install --dry-run`だけでは導入完了ではありません。
30
42
 
31
43
  For a Google Workspace / Google Drive / local-notes setup, pass the known answers and let the command surface what still needs approval:
32
44
 
@@ -354,6 +366,7 @@ brainbase onboard:routines --target codex --cwd /path/to/brainbase
354
366
  brainbase onboard:skills --target codex
355
367
  brainbase ontology:show
356
368
  brainbase ontology:audit
369
+ brainbase judgment:install --target codex --dry-run
357
370
  brainbase doctor
358
371
  ```
359
372
 
@@ -373,6 +386,7 @@ node dist/cli.js onboard:apply --from <candidate-file> --select <id> --write
373
386
  node dist/cli.js onboard:routines --target codex --cwd "$(pwd)"
374
387
  node dist/cli.js onboard:skills --target codex
375
388
  node dist/cli.js onboard:recommend --email gmail --calendar google-calendar --drive google-drive --tasks notion
389
+ node dist/cli.js judgment:install --target codex --dry-run
376
390
  npm run onboard:init
377
391
  npm run onboard:seed -- --name "Your Name"
378
392
  npm run onboard:install -- --target codex --dry-run
@@ -390,6 +404,44 @@ brainbase onboard:seed \
390
404
  --relationship "Key Partner|collaborator|Works with me on AI adoption"
391
405
  ```
392
406
 
407
+ ## Judgment Resolver Host for Codex
408
+
409
+ Brainbase includes a local Judgment Resolver core and a Codex lifecycle Host adapter. The Host starts one portable episode at `UserPromptSubmit`, appends ordered tool events at `PostToolUse`, and finalizes the same episode at `Stop`. It builds one canonical context, adopts exactly one receipt for the turn, and gives the model only the selected judgment nodes and audit contract to follow. The full route receipt remains in the local journal instead of being injected into model context. It does not call a hosted Brainbase service, require a secret, or check project access.
410
+
411
+ Preview the Codex `UserPromptSubmit`, `PostToolUse`, and `Stop` hook snippet:
412
+
413
+ ```bash
414
+ brainbase judgment:install --target codex --dry-run
415
+ ```
416
+
417
+ Review and merge all three printed event bindings into `~/.codex/hooks.json`. The command is preview-only unless `--output` is provided; it never merges into or overwrites an existing config. To save a new snippet file before reviewing it:
418
+
419
+ ```bash
420
+ brainbase judgment:install --target codex --output /tmp/brainbase-judgment-hooks.json
421
+ ```
422
+
423
+ Preserve unrelated hooks, then verify the installed bindings and start a new Codex task:
424
+
425
+ ```bash
426
+ brainbase doctor --dir ~/.brainbase/personal-os --judgment-hooks ~/.codex/hooks.json
427
+ ```
428
+
429
+ After installation, the Host instructs the AI to begin every user-facing response with an exact owner-visible audit line such as:
430
+
431
+ ```text
432
+ 🧠 判断参照: 直前の「ログイン後の白画面を直して」を参照 → 実装依頼として継続 ✓
433
+ ```
434
+
435
+ The line identifies the concrete current or prior user statement used as judgment evidence and the decision made from it. The excerpt is collapsed to one line, limited to 26 Unicode characters, and redacts secret-like assignments and token formats. For example, a question may show `「この仕組みを説明して」を参照 → 質問として回答 ✓`; an unresolved follow-up shows `⚠️ 判断参照: 「それでいい」の対象を特定できず → 確認質問` instead of looking like a successful resolution.
436
+
437
+ Judgment evidence and knowledge-call evidence are separate. A `🧠 判断参照` line says which request the Resolver judged; it never proves that an MCP lookup happened. The Host emits `📚` only after an actual successful portable MCP call and emits `⚠️` for `isError`, malformed, or empty CallToolResult envelopes. The portable mappings are `get_context` → routing (`Brainbase参照先`), `search` → search (`Brainbase検索`), and `search_personal_kg` → retrieval (`Brainbase取得`). Source selection and exclusions appear only when the tool result contains them. When a turn requires no knowledge lookup and none occurred, the exact audit is `📚 Brainbase未参照: 必須参照なし・実呼び出し0回 ✓`; a knowledge-required turn cannot use that line to satisfy retrieval.
438
+
439
+ Detailed receipts, ordered tool events, the final event snapshot, and the exact owner-visible line are journaled together under `~/.brainbase/personal-os/judgment-journal/`, keyed by session and turn. Replayed tool events with the same `tool_use_id` and content are idempotent; conflicting reuse, corrupt journals, and a `Stop` without a matching active episode fail loudly. Only one receipt is adopted for a turn, and later duplicate hook calls reuse the stored line instead of rendering a possibly different summary. The line reports Resolver judgment evidence; it does not claim that Personal OS knowledge was already retrieved.
440
+
441
+ At `Stop`, the Host verifies that all expected audit lines appear at the beginning, exactly once, and in journal order. The first repairable failure returns one bounded block instruction and binds the non-audit answer body by digest. If the active repair changes that body or still omits the required audit contract, the hook exits nonzero with `judgment_stop_repair_exhausted` instead of completing the episode.
442
+
443
+ Every turn is judged, including questions and follow-up instructions. If a follow-up has no usable referent, the receipt selects clarification and the AI asks what the user meant; it does not refuse merely because classification or project context is incomplete. A receipt is judgment evidence, not permission to write files, send messages, deploy, purchase, or perform any other external effect. Normal host permissions and user approvals still apply.
444
+
393
445
  ## Install MCP Config
394
446
 
395
447
  Dry-run output:
package/dist/cli.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  interface CliIo {
3
+ stdin?: AsyncIterable<string | Uint8Array>;
3
4
  stdout?: {
4
5
  write(chunk: string): unknown;
5
6
  };
@@ -8,4 +9,5 @@ interface CliIo {
8
9
  };
9
10
  }
10
11
  export declare function runCli(argv?: string[], io?: CliIo): Promise<number>;
12
+ export declare function isCliEntrypoint(moduleUrl: string, invokedPath: string | undefined): boolean;
11
13
  export {};
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { constants } from 'node:fs';
2
+ import { constants, realpathSync } from 'node:fs';
3
3
  import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
4
4
  import { delimiter, dirname, isAbsolute, join } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
@@ -8,15 +8,20 @@ import { resolveDataDir } from './paths.js';
8
8
  import { auditPersonalOsDirectory } from './ontology-ssot.js';
9
9
  import { portableOntology, resolveOntologyVersion } from './ontology.js';
10
10
  import { onboardingStatus } from './tools.js';
11
- import { buildCandidateDrafts, parseOnboardingFormat, renderAgentProtocol, renderCandidateDrafts, renderConnectorRecommendations, renderLocalOnboardingPlan, renderSourceDiagnosis, renderValueDemo } from './onboarding.js';
11
+ import { buildCandidateDrafts, buildValueDemo, parseOnboardingFormat, renderAgentProtocol, renderCandidateDrafts, renderConnectorRecommendations, renderLocalOnboardingPlan, renderSourceDiagnosis, renderValueDemo } from './onboarding.js';
12
12
  import { buildExtractedCandidateSet, extractCandidates, loadApplyCandidates, normalizeSource, parseProvider, planApply, renderSourceJsonl, sourcePathFor } from './import-extract.js';
13
13
  import { buildClaudeScheduledTasks, buildRoutines, parseRoutineKinds, parseRoutineTarget, renderCodexAutomations } from './routines.js';
14
14
  import { assertPublicSafeSkillBundle, buildSkillBundle, parseSkillIds, parseSkillTarget, renderSkillsMarkdown } from './skills.js';
15
15
  import { buildProjectRegistrationPlan, parseProjectSource, parseProjectStakeholder, renderProjectRegistrationMarkdown } from './projects.js';
16
16
  import { renderGuidedFirstRun } from './guided-onboarding.js';
17
+ import { blockedJudgmentOutput, processJudgmentHook } from './judgment-host.js';
17
18
  export async function runCli(argv = process.argv.slice(2), io = process) {
18
19
  const parsed = parseArgs(argv);
19
20
  try {
21
+ if (parsed.flags.has('help')) {
22
+ write(io, usage());
23
+ return 0;
24
+ }
20
25
  switch (parsed.command) {
21
26
  case 'onboard:init':
22
27
  return await onboardInit(parsed, io);
@@ -55,6 +60,10 @@ export async function runCli(argv = process.argv.slice(2), io = process) {
55
60
  return 0;
56
61
  case 'ontology:audit':
57
62
  return await ontologyAudit(parsed, io);
63
+ case 'judgment:hook':
64
+ return await judgmentHook(io);
65
+ case 'judgment:install':
66
+ return await judgmentInstall(parsed, io);
58
67
  case 'doctor':
59
68
  return await doctor(parsed, io);
60
69
  case 'mcp':
@@ -70,6 +79,59 @@ export async function runCli(argv = process.argv.slice(2), io = process) {
70
79
  return 1;
71
80
  }
72
81
  }
82
+ async function judgmentHook(io) {
83
+ let payload = {};
84
+ try {
85
+ const input = await readHookStdin(io.stdin ?? process.stdin);
86
+ payload = JSON.parse(input || '{}');
87
+ write(io, `${JSON.stringify(await processJudgmentHook(payload))}\n`);
88
+ }
89
+ catch (error) {
90
+ const reason = error instanceof Error ? error.message : String(error);
91
+ const eventName = payload.hook_event_name ?? payload.hookEventName;
92
+ if (eventName === 'Stop' && payload.stop_hook_active === true) {
93
+ writeError(io, `${reason}\n`);
94
+ return 1;
95
+ }
96
+ write(io, `${JSON.stringify(blockedJudgmentOutput(reason))}\n`);
97
+ }
98
+ return 0;
99
+ }
100
+ async function judgmentInstall(parsed, io) {
101
+ const target = first(parsed, 'target');
102
+ if (target !== 'codex')
103
+ throw new Error('judgment:install currently requires --target codex');
104
+ const cliPath = fileURLToPath(new URL('./cli.js', import.meta.url));
105
+ const hook = {
106
+ hooks: [{
107
+ type: 'command',
108
+ command: `${JSON.stringify(process.execPath)} ${JSON.stringify(cliPath)} judgment:hook`,
109
+ statusMessage: 'brainbase judgment resolver'
110
+ }]
111
+ };
112
+ const payload = `${JSON.stringify({
113
+ hooks: {
114
+ UserPromptSubmit: [hook],
115
+ PostToolUse: [hook],
116
+ Stop: [hook]
117
+ }
118
+ }, null, 2)}\n`;
119
+ const outputPath = first(parsed, 'output');
120
+ if (parsed.flags.has('dry-run') || !outputPath) {
121
+ write(io, payload);
122
+ return 0;
123
+ }
124
+ await mkdir(dirname(outputPath), { recursive: true });
125
+ await writeConfigSnippet(outputPath, payload);
126
+ write(io, `Wrote Codex Judgment Host config snippet to ${outputPath}\n`);
127
+ return 0;
128
+ }
129
+ async function readHookStdin(input) {
130
+ let text = '';
131
+ for await (const chunk of input)
132
+ text += typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8');
133
+ return text;
134
+ }
73
135
  async function onboardInit(parsed, io) {
74
136
  const dataDir = resolveDataDir(first(parsed, 'dir'));
75
137
  await initializePersonalOs(dataDir);
@@ -78,8 +140,21 @@ async function onboardInit(parsed, io) {
78
140
  }
79
141
  async function onboardSeed(parsed, io) {
80
142
  const dataDir = resolveDataDir(first(parsed, 'dir'));
81
- await initializePersonalOs(dataDir);
82
143
  const name = first(parsed, 'name');
144
+ const encodedRelationships = parsed.values.get('relationship') ?? [];
145
+ for (const encoded of encodedRelationships) {
146
+ const [person, , context] = encoded.split('|').map((part) => part.trim());
147
+ if (!person || !context) {
148
+ throw new Error([
149
+ '関係者の形式が正しくありません。',
150
+ '形式: "人|役割|覚えておく文脈"',
151
+ '例: "田中|責任者|Atlas導入の最終判断を担当"',
152
+ '保存内容は変更していません。',
153
+ `再実行: ${seedRetryCommand(parsed, dataDir, encoded)}`
154
+ ].join('\n'));
155
+ }
156
+ }
157
+ await initializePersonalOs(dataDir);
83
158
  const hasSeedValues = Boolean(name)
84
159
  || ['value', 'project', 'decision-principle', 'relationship'].some((key) => (parsed.values.get(key)?.length ?? 0) > 0);
85
160
  if (!parsed.flags.has('non-interactive') && !hasSeedValues) {
@@ -88,8 +163,8 @@ async function onboardSeed(parsed, io) {
88
163
  }
89
164
  await mutatePersonalOs(dataDir, (os) => {
90
165
  const now = new Date().toISOString();
91
- const personalEntries = [];
92
- const decisions = [];
166
+ const personalEntries = [...os.personalKg];
167
+ const decisions = [...os.decisions];
93
168
  const relationships = [...os.relationships.relationships];
94
169
  const graphEntities = [...os.graph.entities];
95
170
  if (name) {
@@ -98,19 +173,19 @@ async function onboardSeed(parsed, io) {
98
173
  id: 'self',
99
174
  type: 'person',
100
175
  name,
101
- summary: 'Owner of this local Brainbase Personal OS.',
176
+ summary: 'このPersonal OSの本人。',
102
177
  tags: ['self']
103
178
  });
104
- personalEntries.push({
105
- id: `self-${Date.now()}`,
179
+ upsertById(personalEntries, {
180
+ id: `self-${hash(name)}`,
106
181
  type: 'self',
107
- text: `I am ${name}.`,
182
+ text: `私は${name}です。`,
108
183
  tags: ['self'],
109
184
  updatedAt: now
110
185
  });
111
186
  }
112
187
  for (const value of parsed.values.get('value') ?? []) {
113
- personalEntries.push({
188
+ upsertById(personalEntries, {
114
189
  id: `value-${hash(value)}`,
115
190
  type: 'value',
116
191
  text: value,
@@ -123,10 +198,10 @@ async function onboardSeed(parsed, io) {
123
198
  id: `project-${hash(project)}`,
124
199
  type: 'project',
125
200
  name: project,
126
- summary: 'Seeded during Brainbase onboarding.',
201
+ summary: '現在取り組んでいるプロジェクト。',
127
202
  tags: ['work']
128
203
  });
129
- personalEntries.push({
204
+ upsertById(personalEntries, {
130
205
  id: `work-${hash(project)}`,
131
206
  type: 'work',
132
207
  text: project,
@@ -135,20 +210,17 @@ async function onboardSeed(parsed, io) {
135
210
  });
136
211
  }
137
212
  for (const value of parsed.values.get('decision-principle') ?? []) {
138
- decisions.push({
213
+ upsertById(decisions, {
139
214
  id: `decision-${hash(value)}`,
140
- title: 'Seeded decision principle',
215
+ title: 'オンボーディングで登録した判断基準',
141
216
  decision: value,
142
217
  tags: ['principle', 'onboarding'],
143
218
  updatedAt: now
144
219
  });
145
220
  }
146
- for (const encoded of parsed.values.get('relationship') ?? []) {
221
+ for (const encoded of encodedRelationships) {
147
222
  const [person, role, context] = encoded.split('|').map((part) => part.trim());
148
- if (!person || !context) {
149
- throw new Error('relationship must be "person|role|context" or "person||context"');
150
- }
151
- relationships.push({
223
+ upsertById(relationships, {
152
224
  id: `relationship-${hash(encoded)}`,
153
225
  person,
154
226
  role: role || undefined,
@@ -167,13 +239,46 @@ async function onboardSeed(parsed, io) {
167
239
  return proposedPersonalOs(os, {
168
240
  graph: { ...os.graph, entities: graphEntities },
169
241
  relationships: { version: 1, relationships },
170
- personalKg: [...os.personalKg, ...personalEntries],
171
- decisions: [...os.decisions, ...decisions]
242
+ personalKg: personalEntries,
243
+ decisions
172
244
  });
173
245
  });
174
- write(io, `Seeded Brainbase Personal OS at ${dataDir}\n`);
246
+ const summary = [
247
+ `Brainbaseへ保存しました: ${dataDir}`,
248
+ ...(name ? [`- 本人: ${name}`] : []),
249
+ ...(parsed.values.get('value') ?? []).map((value) => `- 価値観: ${value}`),
250
+ ...(parsed.values.get('project') ?? []).map((project) => `- プロジェクト: ${project}`),
251
+ ...encodedRelationships.map((encoded) => {
252
+ const [person, role] = encoded.split('|').map((part) => part.trim());
253
+ return `- 関係者: ${person}${role ? `(${role})` : ''}`;
254
+ }),
255
+ ...(parsed.values.get('decision-principle') ?? []).map((value) => `- 判断基準: ${value}`),
256
+ '- 同じ文脈は更新しました。既存の別データは削除していません。',
257
+ '',
258
+ '次に実行:',
259
+ `brainbase onboard:demo --dir ${shellArg(dataDir)} --scenario "保存した文脈を使って、次に進めるメモを作って"`,
260
+ ''
261
+ ];
262
+ write(io, summary.join('\n'));
175
263
  return 0;
176
264
  }
265
+ function seedRetryCommand(parsed, dataDir, invalidRelationship) {
266
+ const args = ['brainbase', 'onboard:seed', '--dir', dataDir];
267
+ const append = (flag, value) => {
268
+ if (value)
269
+ args.push(flag, value);
270
+ };
271
+ append('--name', first(parsed, 'name'));
272
+ append('--value', first(parsed, 'value'));
273
+ append('--project', first(parsed, 'project'));
274
+ append('--decision-principle', first(parsed, 'decision-principle'));
275
+ for (const relationship of parsed.values.get('relationship') ?? []) {
276
+ if (relationship !== invalidRelationship)
277
+ append('--relationship', relationship);
278
+ }
279
+ append('--relationship', '田中|責任者|Atlas導入の最終判断を担当');
280
+ return args.map(shellArg).join(' ');
281
+ }
177
282
  async function onboardInstall(parsed, io) {
178
283
  const target = first(parsed, 'target');
179
284
  if (!isInstallTarget(target)) {
@@ -239,9 +344,9 @@ async function onboardStart(parsed, io) {
239
344
  assumeGog: parsed.flags.has('assume-gog'),
240
345
  gogAvailable,
241
346
  project,
242
- connected: status.connected === true,
347
+ connected: readLocalBackendConnected(status),
243
348
  missing
244
- }, format));
349
+ }, format, parsed.flags.has('details')));
245
350
  return 0;
246
351
  }
247
352
  async function onboardDemo(parsed, io) {
@@ -249,8 +354,9 @@ async function onboardDemo(parsed, io) {
249
354
  const dataDir = resolveDataDir(first(parsed, 'dir'));
250
355
  await initializePersonalOs(dataDir);
251
356
  const os = await loadPersonalOs(dataDir);
252
- write(io, renderValueDemo({ os, scenario: first(parsed, 'scenario') }, format));
253
- return 0;
357
+ const demo = buildValueDemo({ os, scenario: first(parsed, 'scenario') });
358
+ write(io, renderValueDemo({ os, scenario: first(parsed, 'scenario') }, format, parsed.flags.has('details')));
359
+ return demo.ready ? 0 : 1;
254
360
  }
255
361
  async function onboardRecommend(parsed, io) {
256
362
  const format = parseOnboardingFormat(first(parsed, 'format'));
@@ -713,11 +819,35 @@ async function writeConfigSnippet(outputPath, payload) {
713
819
  async function doctor(parsed, io) {
714
820
  const dataDir = resolveDataDir(first(parsed, 'dir'));
715
821
  const os = await loadPersonalOs(dataDir);
716
- write(io, `${JSON.stringify(onboardingStatus(os), null, 2)}\n`);
822
+ const status = onboardingStatus(os);
823
+ const judgmentHooksPath = first(parsed, 'judgment-hooks');
824
+ if (!judgmentHooksPath) {
825
+ write(io, `${JSON.stringify(status, null, 2)}\n`);
826
+ return 0;
827
+ }
828
+ const config = JSON.parse(await readFile(judgmentHooksPath, 'utf8'));
829
+ const hooks = config.hooks;
830
+ const requiredEvents = ['UserPromptSubmit', 'PostToolUse', 'Stop'];
831
+ const valid = Boolean(hooks) && requiredEvents.every((eventName) => {
832
+ const bindings = hooks?.[eventName];
833
+ return Array.isArray(bindings) && bindings.some((binding) => {
834
+ const commands = binding.hooks;
835
+ return Array.isArray(commands) && commands.some((command) => (typeof command.command === 'string'
836
+ && command.command.includes('judgment:hook')));
837
+ });
838
+ });
839
+ if (!valid)
840
+ throw new Error('judgment_hooks_invalid');
841
+ write(io, `${JSON.stringify({
842
+ ...status,
843
+ judgment_hooks: { status: 'ready', events: requiredEvents, source: judgmentHooksPath }
844
+ }, null, 2)}\n`);
717
845
  return 0;
718
846
  }
719
847
  function parseArgs(argv) {
720
- const [command, ...rest] = argv;
848
+ const [firstToken, ...remaining] = argv;
849
+ const command = firstToken?.startsWith('--') ? undefined : firstToken;
850
+ const rest = command ? remaining : argv;
721
851
  const values = new Map();
722
852
  const flags = new Set();
723
853
  for (let index = 0; index < rest.length; index += 1) {
@@ -743,6 +873,13 @@ function parseArgs(argv) {
743
873
  function first(parsed, key) {
744
874
  return parsed.values.get(key)?.[0];
745
875
  }
876
+ function readLocalBackendConnected(status) {
877
+ const localBackend = status.localBackend;
878
+ return typeof localBackend === 'object'
879
+ && localBackend !== null
880
+ && 'connected' in localBackend
881
+ && localBackend.connected === true;
882
+ }
746
883
  async function commandExists(command) {
747
884
  const candidates = isAbsolute(command)
748
885
  ? [command]
@@ -758,15 +895,18 @@ async function commandExists(command) {
758
895
  }
759
896
  return false;
760
897
  }
761
- function upsertGraphEntity(entities, entity) {
762
- const index = entities.findIndex((candidate) => candidate.id === entity.id);
898
+ function upsertById(entries, entry) {
899
+ const index = entries.findIndex((candidate) => candidate.id === entry.id);
763
900
  if (index >= 0) {
764
- entities[index] = entity;
901
+ entries[index] = entry;
765
902
  }
766
903
  else {
767
- entities.push(entity);
904
+ entries.push(entry);
768
905
  }
769
906
  }
907
+ function upsertGraphEntity(entities, entity) {
908
+ upsertById(entities, entity);
909
+ }
770
910
  function hash(value) {
771
911
  let hashValue = 0;
772
912
  for (const char of value) {
@@ -794,15 +934,20 @@ function proposedPersonalOs(os, proposed) {
794
934
  return { ...os, ...proposed };
795
935
  }
796
936
  function usage() {
797
- return `Usage:
937
+ return `最短で試す(3ステップ):
938
+ 1. brainbase onboard:start --target codex
939
+ 2. 表示された brainbase onboard:seed を確認して実行
940
+ 3. brainbase onboard:demo --scenario "実際に試す依頼"
941
+
942
+ 使い方:
798
943
  brainbase-mcp
799
944
  brainbase mcp
800
945
  brainbase onboard:init [--dir path]
801
946
  brainbase onboard:seed [--dir path] [--name value] [--value value] [--project value] [--decision-principle value] [--relationship "person|role|context"]
802
947
  brainbase onboard:install --target codex|claude|codecode [--dir path] [--dry-run] [--output path]
803
948
  brainbase onboard:agent [--format markdown|json]
804
- brainbase onboard:start [--target codex|claude|codecode] [--dir path] [--name value] [--project value] [--goal value] [--status value] [--role value] [--email value] [--calendar value] [--drive value] [--drive-folder id] [--local-folder path] [--tasks value] [--format markdown|json]
805
- brainbase onboard:demo [--dir path] [--scenario value] [--format markdown|json]
949
+ brainbase onboard:start [--target codex|claude|codecode] [--dir path] [--name value] [--value value] [--project value] [--decision-principle value] [--goal value] [--status value] [--role value] [--email value] [--calendar value] [--drive value] [--drive-folder id] [--local-folder path] [--tasks value] [--format markdown|json] [--details]
950
+ brainbase onboard:demo [--dir path] [--scenario value] [--format markdown|json] [--details]
806
951
  brainbase onboard:recommend [--email value] [--calendar value] [--drive value] [--tasks value] [--format markdown|json]
807
952
  brainbase onboard:diagnose-sources [--dir path] [--email value] [--calendar value] [--drive value] [--drive-folder id] [--tasks value] [--assume-gog] [--gog-command command] [--format markdown|json]
808
953
  brainbase onboard:plan [--profile google-workspace-local] [--host value] [--email value] [--secondary-email value] [--calendar value] [--drive value] [--drive-folder id] [--local-folder path] [--tasks value] [--inactive-task-tool value] [--format markdown|json]
@@ -815,10 +960,25 @@ function usage() {
815
960
  brainbase onboard:skills --target codex|claude|portable [--skills id,id] [--out dir] [--format markdown|json]
816
961
  brainbase ontology:show
817
962
  brainbase ontology:audit [--dir path] [--ontology-version 0.0.0|1.0.0]
818
- brainbase doctor [--dir path]
963
+ brainbase judgment:install --target codex [--dry-run] [--output path]
964
+ brainbase judgment:hook
965
+ brainbase doctor [--dir path] [--judgment-hooks path]
819
966
  `;
820
967
  }
821
- if (import.meta.url === `file://${process.argv[1]}`) {
968
+ function shellArg(value) {
969
+ return /^[A-Za-z0-9_./:@%+=,-]+$/.test(value) ? value : JSON.stringify(value);
970
+ }
971
+ export function isCliEntrypoint(moduleUrl, invokedPath) {
972
+ if (!invokedPath)
973
+ return false;
974
+ try {
975
+ return realpathSync(fileURLToPath(moduleUrl)) === realpathSync(invokedPath);
976
+ }
977
+ catch {
978
+ return false;
979
+ }
980
+ }
981
+ if (isCliEntrypoint(import.meta.url, process.argv[1])) {
822
982
  const code = await runCli();
823
983
  process.exit(code);
824
984
  }
@@ -0,0 +1,123 @@
1
+ declare const WORKFLOW_STATES: readonly ["initialized", "source_ready", "candidates_ready", "promotion_reviewed", "first_value_ready", "first_value_answer_reviewed"];
2
+ export type SourceMode = 'mcp' | 'drive' | 'gmail' | 'local_folder' | 'single_document';
3
+ export type SourceStatus = 'ready' | 'waiting_for_authorization' | 'unavailable' | 'error' | 'unconfirmed';
4
+ export type ObservationClass = 'observed' | 'inferred';
5
+ export type ReviewDecision = 'approve' | 'edit' | 'reject' | 'merge';
6
+ export type WorkflowState = typeof WORKFLOW_STATES[number];
7
+ export interface SourceInventoryInput {
8
+ id: string;
9
+ mode: SourceMode;
10
+ status: SourceStatus;
11
+ evidencePointer?: string;
12
+ permissionScope?: string[];
13
+ detail?: string;
14
+ }
15
+ export interface SourceInventory extends SourceInventoryInput {
16
+ id: string;
17
+ permissionScope: string[];
18
+ }
19
+ export interface SourceReceiptInput {
20
+ sourceId: string;
21
+ evidencePointer: string;
22
+ contentHash: string;
23
+ permissionSnapshot: Record<string, unknown>;
24
+ collectionStatus: 'collected';
25
+ }
26
+ export interface CandidateInput {
27
+ kind: string;
28
+ payload: Record<string, unknown>;
29
+ observationClass: ObservationClass;
30
+ evidenceId: string;
31
+ }
32
+ export interface ConnectedCandidate extends CandidateInput {
33
+ id: string;
34
+ ingestedPayloadHash: string;
35
+ sourceId: string;
36
+ reviewStatus: 'pending' | 'approved' | 'rejected' | 'merged';
37
+ reviewDecision?: ReviewDecision;
38
+ reviewReason?: string;
39
+ mergedIntoCandidateId?: string;
40
+ promotedCanonicalIds: string[];
41
+ }
42
+ export interface SourceReceipt extends SourceReceiptInput {
43
+ mode: SourceMode;
44
+ collectedAt: string;
45
+ candidateIds: string[];
46
+ }
47
+ export interface FirstValueReceipt {
48
+ answerHash: string;
49
+ usedCanonicalIds: string[];
50
+ missingContext: string[];
51
+ recordedAt: string;
52
+ }
53
+ export interface FirstValueReview {
54
+ verdict: 'useful' | 'not_useful';
55
+ missingContext: string[];
56
+ reviewedAt: string;
57
+ elapsedSeconds: number;
58
+ withinTargetSeconds: boolean;
59
+ }
60
+ export interface ConnectedOnboardingRun {
61
+ id: string;
62
+ valueTarget: string;
63
+ path: 'warm' | 'fallback' | 'blocked';
64
+ state: WorkflowState;
65
+ startedAt: string;
66
+ updatedAt: string;
67
+ sourceReadyAt?: string;
68
+ sources: SourceInventory[];
69
+ selectedSourceIds: string[];
70
+ receipts: SourceReceipt[];
71
+ candidates: ConnectedCandidate[];
72
+ promotedCanonicalIds: string[];
73
+ firstValueReceipt?: FirstValueReceipt;
74
+ firstValueReview?: FirstValueReview;
75
+ }
76
+ export interface ConnectedOnboardingOptions {
77
+ now?: () => Date;
78
+ idFactory?: () => string;
79
+ }
80
+ export interface ReviewAction {
81
+ candidateId: string;
82
+ decision: ReviewDecision;
83
+ reason: string;
84
+ payload?: Record<string, unknown>;
85
+ mergeIntoCandidateId?: string;
86
+ }
87
+ export type FirstValueInput = {
88
+ action: 'record';
89
+ answerHash: string;
90
+ usedCanonicalIds: string[];
91
+ missingContext?: string[];
92
+ } | {
93
+ action: 'review';
94
+ verdict: 'useful' | 'not_useful';
95
+ missingContext?: string[];
96
+ };
97
+ export declare class ConnectedOnboardingError extends Error {
98
+ readonly code: string;
99
+ constructor(code: string, message: string);
100
+ }
101
+ export declare class ConnectedOnboardingRuntime {
102
+ private readonly dataDir;
103
+ private readonly now;
104
+ private readonly idFactory;
105
+ constructor(dataDir: string, options?: ConnectedOnboardingOptions);
106
+ start(input: {
107
+ valueTarget: string;
108
+ sources: SourceInventoryInput[];
109
+ }): Promise<ConnectedOnboardingRun>;
110
+ get(runId: string): Promise<ConnectedOnboardingRun>;
111
+ ingest(runId: string, input: {
112
+ source: SourceReceiptInput;
113
+ candidates: CandidateInput[];
114
+ }): Promise<ConnectedOnboardingRun>;
115
+ review(runId: string, actions: ReviewAction[]): Promise<ConnectedOnboardingRun>;
116
+ firstValue(runId: string, input: FirstValueInput): Promise<ConnectedOnboardingRun>;
117
+ private timestamp;
118
+ private readLedger;
119
+ private mutateLedger;
120
+ }
121
+ export declare function normalizeSourceInventory(raw: SourceInventoryInput[]): SourceInventory[];
122
+ export declare function assertSafePersistentValue(value: unknown, trail?: string[]): void;
123
+ export {};