@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/README.md
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
# Brainbase
|
|
1
|
+
# Brainbase 個人オンボーディングキット
|
|
2
2
|
|
|
3
|
-
Brainbase
|
|
3
|
+
Brainbaseは、自分が承認した仕事の前提をCodex、Claude Code、CodeCodeへ渡すための、ローカル優先のMCPサーバーです。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
最初の目標は情報源をすべて接続することではありません。自分、仕事、関係者、判断基準の最小文脈を保存し、10分以内に「同じ前提を説明し直さず役立つ出力」を確認することです。
|
|
6
6
|
|
|
7
|
-
Ontology 1.0.0
|
|
7
|
+
Ontology 1.0.0は、ローカルファイルへ持ち運べる意味契約を追加します。ホスト型Brainbaseを必要とせず、型、関係語彙、検証制約、決定論的な判断推論、バージョン移行を定義します。
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
このリポジトリに、社内BrainbaseのUI、セッション実行基盤、xterm転送、ワークフロー管制、SNS運用、ホスト型バックエンド、Infisical設定、雲孫の社内データは含みません。それらは社内版`brainbase-unson`の範囲です。
|
|
10
10
|
|
|
11
|
-
##
|
|
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
|
-
##
|
|
19
|
+
## エージェントと始める
|
|
18
20
|
|
|
19
|
-
Brainbase
|
|
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
|
|
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
|
-
|
|
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: '
|
|
176
|
+
summary: 'このPersonal OSの本人。',
|
|
102
177
|
tags: ['self']
|
|
103
178
|
});
|
|
104
|
-
personalEntries
|
|
105
|
-
id: `self-${
|
|
179
|
+
upsertById(personalEntries, {
|
|
180
|
+
id: `self-${hash(name)}`,
|
|
106
181
|
type: 'self',
|
|
107
|
-
text:
|
|
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
|
|
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: '
|
|
201
|
+
summary: '現在取り組んでいるプロジェクト。',
|
|
127
202
|
tags: ['work']
|
|
128
203
|
});
|
|
129
|
-
personalEntries
|
|
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
|
|
213
|
+
upsertById(decisions, {
|
|
139
214
|
id: `decision-${hash(value)}`,
|
|
140
|
-
title: '
|
|
215
|
+
title: 'オンボーディングで登録した判断基準',
|
|
141
216
|
decision: value,
|
|
142
217
|
tags: ['principle', 'onboarding'],
|
|
143
218
|
updatedAt: now
|
|
144
219
|
});
|
|
145
220
|
}
|
|
146
|
-
for (const encoded of
|
|
221
|
+
for (const encoded of encodedRelationships) {
|
|
147
222
|
const [person, role, context] = encoded.split('|').map((part) => part.trim());
|
|
148
|
-
|
|
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:
|
|
171
|
-
decisions
|
|
242
|
+
personalKg: personalEntries,
|
|
243
|
+
decisions
|
|
172
244
|
});
|
|
173
245
|
});
|
|
174
|
-
|
|
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
|
|
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
|
-
|
|
253
|
-
|
|
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
|
-
|
|
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 [
|
|
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
|
|
762
|
-
const index =
|
|
898
|
+
function upsertById(entries, entry) {
|
|
899
|
+
const index = entries.findIndex((candidate) => candidate.id === entry.id);
|
|
763
900
|
if (index >= 0) {
|
|
764
|
-
|
|
901
|
+
entries[index] = entry;
|
|
765
902
|
}
|
|
766
903
|
else {
|
|
767
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 {};
|