@unson/brainbase-mcp 0.15.6 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,6 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { fileURLToPath } from 'node:url';
3
- import { buildJudgmentValueProofReviewHome, readJudgmentValueProofAnswers, readJudgmentValueProofFeedback, readJudgmentValueProofJournal, } from './judgment-value-proof-review.js';
4
- import { publicHome } from './value-proof-review-http.js';
3
+ import { readJudgmentValueProofJournal, } from './judgment-value-proof-review.js';
5
4
  import { projectOrganizationGraph, projectVocabularyTerms } from './organization-graph-web.js';
6
5
  import { readLocalGraphState } from './local-web-host.js';
7
6
  import { loadPersonalOs } from './ssot.js';
@@ -224,37 +223,26 @@ export async function readWorldVocabulary(path) {
224
223
  return { kinds, statuses };
225
224
  }
226
225
  /**
227
- * The businesses from the same Graph the other screens read: the organization
228
- * Graph when the host reads it (C1), else the local Graph. A Graph that cannot
229
- * be read is a state with its reason, never zero businesses.
226
+ * The businesses from the same Graph the other screens read (this Mac's Graph).
227
+ * An organization host draws its own records with `projectOrganizationWorld`.
228
+ * A Graph that cannot be read is a state with its reason, never zero businesses.
230
229
  */
231
230
  export async function readWorldBusinesses(context, options = {}) {
232
231
  const now = (options.now ?? (() => new Date()))();
233
232
  let graph;
234
233
  let source;
235
- let terms;
236
234
  try {
237
- if (context.organizationGraph) {
238
- if (!context.organizationGraph.readGraphFile) {
239
- return { status: 'unavailable', version: WORLD_EXTENSION_VERSION, reason: 'organization_graph_records_unavailable' };
240
- }
241
- graph = await context.organizationGraph.readGraphFile();
242
- source = { authority: 'organization_graph', server: context.organizationGraph.server };
243
- terms = context.organizationGraph.readVocabularyTerms ? await context.organizationGraph.readVocabularyTerms().catch(() => null) : null;
244
- }
245
- else {
246
- const state = await readLocalGraphState(context.dataDir);
247
- if (state.status === 'not_initialized' || state.status === 'migration_required') {
248
- return { status: state.status, version: WORLD_EXTENSION_VERSION, reason: state.status };
249
- }
250
- if (state.status !== 'ready')
251
- return { status: 'unavailable', version: WORLD_EXTENSION_VERSION, reason: state.message ?? 'local_graph_unreadable' };
252
- const os = await loadPersonalOs(context.dataDir);
253
- if (os.graph.version !== 2)
254
- return { status: 'migration_required', version: WORLD_EXTENSION_VERSION, reason: 'migration_required' };
255
- graph = os.graph;
256
- source = { authority: 'local', dataDir: context.dataDir };
235
+ const state = await readLocalGraphState(context.dataDir);
236
+ if (state.status === 'not_initialized' || state.status === 'migration_required') {
237
+ return { status: state.status, version: WORLD_EXTENSION_VERSION, reason: state.status };
257
238
  }
239
+ if (state.status !== 'ready')
240
+ return { status: 'unavailable', version: WORLD_EXTENSION_VERSION, reason: state.message ?? 'local_graph_unreadable' };
241
+ const os = await loadPersonalOs(context.dataDir);
242
+ if (os.graph.version !== 2)
243
+ return { status: 'migration_required', version: WORLD_EXTENSION_VERSION, reason: 'migration_required' };
244
+ graph = os.graph;
245
+ source = { authority: 'local', dataDir: context.dataDir };
258
246
  }
259
247
  catch (error) {
260
248
  return { status: 'unavailable', version: WORLD_EXTENSION_VERSION, reason: error instanceof Error ? error.message : 'graph_unreadable' };
@@ -265,7 +253,7 @@ export async function readWorldBusinesses(context, options = {}) {
265
253
  version: WORLD_EXTENSION_VERSION,
266
254
  source,
267
255
  as_of: now.toISOString(),
268
- vocabulary: resolveWorldVocabulary(options.vocabulary, projection.businesses, terms),
256
+ vocabulary: resolveWorldVocabulary(options.vocabulary, projection.businesses),
269
257
  ...projection,
270
258
  };
271
259
  }
@@ -315,44 +303,6 @@ export async function readJudgmentPlaces(journalRoot, options = {}) {
315
303
  }));
316
304
  return { status: 'available', places };
317
305
  }
318
- /**
319
- * The judgments of one organization, for a world drawn from that organization's Graph. A judgment
320
- * belongs to the organization only when the repository it was worked in is one of the organization's
321
- * businesses (a registered repository or the business code, the same rule as the world's placement).
322
- * Every other judgment (another company's work, or one whose repository was not recorded) is withheld:
323
- * only their count leaves the host, never their questions or repositories.
324
- */
325
- export async function readOrganizationJudgments(context, businesses, workspaces = new Map()) {
326
- const journal = await readJudgmentValueProofJournal({ root: context.journalRoot, cache: context.journalCache });
327
- if (journal.status !== 'available')
328
- return { status: 'unavailable', scope: 'organization', withheld: 0, places: [], home: null, reason: 'journal_unreadable' };
329
- const belongs = (workspace) => workspace !== null
330
- && businesses.some((business) => business.repositories.includes(workspace) || business.code === workspace);
331
- const kept = [];
332
- const places = [];
333
- for (const entry of journal.entries) {
334
- const workspace = await entryWorkspace(entry.file, workspaces);
335
- if (!belongs(workspace))
336
- continue;
337
- kept.push(entry);
338
- places.push({ decision_attempt_id: entry.proof.decision_attempt_id, workspace });
339
- }
340
- const [feedback, answers] = await Promise.all([
341
- readJudgmentValueProofFeedback({ dataDir: context.dataDir }),
342
- readJudgmentValueProofAnswers({ dataDir: context.dataDir }),
343
- ]);
344
- // Rejected files are left out too: their names would say nothing about the organization they belong to.
345
- const home = buildJudgmentValueProofReviewHome({ ...journal, entries: kept, rejected: [] }, feedback, { now: context.now(), answers });
346
- return { status: 'available', scope: 'organization', withheld: journal.entries.length - kept.length, places, home: publicHome(home) };
347
- }
348
- async function entryWorkspace(valueProofFile, workspaces) {
349
- const turnInputFile = `${valueProofFile.slice(0, -VALUE_PROOF_SUFFIX.length)}.turn-input.json`;
350
- if (!workspaces.has(turnInputFile)) {
351
- const turnInput = await readJsonFile(turnInputFile);
352
- workspaces.set(turnInputFile, isRecord(turnInput) ? text(turnInput.project_code) : null);
353
- }
354
- return workspaces.get(turnInputFile) ?? null;
355
- }
356
306
  async function readJsonFile(path) {
357
307
  try {
358
308
  return JSON.parse(await readFile(path, 'utf8'));
@@ -375,16 +325,6 @@ function createWorldModule(context, read) {
375
325
  writeJson(response, 200, await read());
376
326
  return true;
377
327
  }
378
- if (path === `/api/extensions/${WORLD_EXTENSION_ID}/organization-judgments`) {
379
- response.setHeader('Cache-Control', 'no-store');
380
- const world = await read();
381
- if (world.status !== 'ok' || world.source.authority !== 'organization_graph') {
382
- writeJson(response, 200, { version: WORLD_EXTENSION_VERSION, status: 'unavailable', scope: 'organization', withheld: 0, places: [], home: null, reason: 'not_reading_organization_graph' });
383
- return true;
384
- }
385
- writeJson(response, 200, { version: WORLD_EXTENSION_VERSION, ...(await readOrganizationJudgments(context, world.businesses, workspaces)) });
386
- return true;
387
- }
388
328
  if (path === `/api/extensions/${WORLD_EXTENSION_ID}/judgment-places`) {
389
329
  response.setHeader('Cache-Control', 'no-store');
390
330
  writeJson(response, 200, { version: WORLD_EXTENSION_VERSION, ...(await readJudgmentPlaces(context.journalRoot, { cache: context.journalCache, workspaces })) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unson/brainbase-mcp",
3
- "version": "0.15.6",
3
+ "version": "0.16.0",
4
4
  "description": "Local-first Brainbase judgment OS that lets supported AI agents reuse a user's approved goals, priorities, decisions, and judgment criteria.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -358,5 +358,5 @@
358
358
  "engines": {
359
359
  "node": ">=20"
360
360
  },
361
- "gitHead": "94fabc0ef66f6296d9f68fb5e4e54d87a63fc65b"
361
+ "gitHead": "a0914d025914d4ada329b730aeddd6488f28bcfd"
362
362
  }
@@ -469,45 +469,6 @@ export function hostSourceNotice(sourceNotice, subject) {
469
469
  return { ...sourceNotice, text: sourceNotice.text(subject ?? null) };
470
470
  }
471
471
 
472
- // ---------------------------------------------------------------------------
473
- // The organization's web (Inspector UX-20261004-01)
474
-
475
- const ORGANIZATION_RECORD_ID = /^[A-Za-z0-9_.:-]{1,200}$/u;
476
-
477
- /** The organization web's origin the host was told about, or null. */
478
- export function organizationWebOrigin(organizationGraph) {
479
- if (organizationGraph?.status !== 'connected') return null;
480
- const origin = organizationGraph.web_url;
481
- return typeof origin === 'string' && /^https:\/\/[^/?#@\s]+$/u.test(origin) ? origin : null;
482
- }
483
-
484
- /**
485
- * Where a record opens in the organization web, `{ href, label }`, or null when it has no usable id. The
486
- * organization web opens 情報と関係 by record id and プロジェクトと関係者 by project code, and keeps that record
487
- * across sign-in. A project opens by its own code only: a sub-project's scope code is its parent's and
488
- * would open the parent, so without its own code the record opens by id.
489
- */
490
- export function organizationRecordLink(origin, screenId, record) {
491
- const id = typeof record?.id === 'string' && ORGANIZATION_RECORD_ID.test(record.id) ? record.id : null;
492
- const own = typeof record?.metadata?.code === 'string' ? record.metadata.code.trim() : '';
493
- const code = own && ORGANIZATION_RECORD_ID.test(own) ? own : null;
494
- if (screenId === 'projects' && record?.type === 'project' && code) {
495
- return { href: `${origin}/?screen=projects&project=${encodeURIComponent(code)}`, label: '「プロジェクトと関係者」' };
496
- }
497
- return id ? { href: `${origin}/?screen=graph&entity_id=${encodeURIComponent(id)}`, label: '「情報と関係」' } : null;
498
- }
499
-
500
- export const ORGANIZATION_RECORD_LINK_TEXT = '組織版でこの記録を開く';
501
-
502
- /** A link that opens `target` from organizationRecordLink in a new tab. */
503
- export function organizationRecordAnchor(doc, target, className) {
504
- return makeElement(doc, 'a', {
505
- ...(className ? { className } : {}),
506
- text: ORGANIZATION_RECORD_LINK_TEXT,
507
- attrs: { href: target.href, target: '_blank', rel: 'noopener noreferrer' },
508
- });
509
- }
510
-
511
472
  /**
512
473
  * A host's note that corrections are not available here, or null when it passed none. The host may
513
474
  * pass a function of the selected record (`{ id, type, metadata }`) returning a string or an element,
@@ -21,7 +21,7 @@ import { createObjectiveEditorController } from './objective-editor.js';
21
21
  import { createObjectiveEditorHttpPort } from './objective-editor-http-port.js';
22
22
  import { createValueProofReviewUI } from './value-proof-review.js';
23
23
  import { createWorldModelView } from './world-model-view.js';
24
- import { createGraphClient, organizationRecordAnchor, organizationRecordLink, organizationWebOrigin } from './graph-view-shared.js';
24
+ import { createGraphClient } from './graph-view-shared.js';
25
25
 
26
26
  export const LOCAL_WEB_SHELL_CONTRACT_VERSION = 'brainbase.local-web-shell.v1';
27
27
 
@@ -203,58 +203,11 @@ function mountObjectives(container, context) {
203
203
  return { editor, worldModel };
204
204
  }
205
205
 
206
- const ORGANIZATION_READ_ONLY_NOTE = '組織のGraphを読み取り専用で表示しています。登録の訂正は組織版で行ってください。';
207
- // What the owner needs before leaving for the organization web (Inspector UX-20261004-01): a link to this
208
- // very record there, who may correct it, and what happens after saving.
209
- export { organizationRecordLink };
210
- const organizationReadOnlyNoteLinked = (doc, origin, screenId) => (record) => {
211
- const target = organizationRecordLink(origin, screenId, record);
212
- if (!target) return ORGANIZATION_READ_ONLY_NOTE;
213
- const note = makeElement(doc, 'span');
214
- note.append(
215
- makeElement(doc, 'span', { text: [
216
- '組織のGraphを読み取り専用で表示しています。ここでは直せません。訂正の手順:',
217
- `① 下のリンク(上の「出典」にも同じリンクがあります)から、組織版の${target.label}でこの記録を開きます。ログインしていなければSlackでログインすると、そのままこの記録に戻ります。`,
218
- '② 直せるのは、組織版での役割がgmかceoで、このプロジェクトが属する事業(事業そのものなら、その事業)を許可されている人です。直せるのはプロジェクトの記録(名前・別名・要約・期間・目的・状態)だけで、人物の記録と、関係者の追加・終了は組織版でもまだ直せません。',
219
- '③ 保存すると、理由といっしょに組織のGraphへ書き込まれ、組織版がその場で読み戻して表示します。この画面には、1分ほどたってから再読み込みすると反映されます。',
220
- '',
221
- ].join('\n') }),
222
- organizationRecordAnchor(doc, target),
223
- );
224
- return note;
225
- };
226
-
227
- /**
228
- * The 出典 notice of a screen reading the organization Graph. Once a record is selected its link opens
229
- * that record in the organization web; before that, the screen there.
230
- */
231
- const organizationSourceText = (doc, origin, screenId, sourceText) => (record) => {
232
- const target = record ? organizationRecordLink(origin, screenId, record) : null;
233
- const link = target
234
- ? organizationRecordAnchor(doc, target)
235
- : makeElement(doc, 'a', {
236
- text: '組織版で開いて訂正する',
237
- attrs: { href: `${origin}/?screen=${encodeURIComponent(screenId)}`, target: '_blank', rel: 'noopener noreferrer' },
238
- });
239
- const source = makeElement(doc, 'span');
240
- source.append(makeElement(doc, 'span', { text: `${sourceText} ` }), link);
241
- return source;
242
- };
243
-
244
206
  function mountGraphScreen(screenId, createView) {
245
207
  return (container, context) => {
246
208
  const viewRoot = makeElement(context.document, 'div');
247
209
  makePage(container, context).append(viewRoot);
248
- // Ledger C1: when the host reads the organization Graph, nothing here can be corrected.
249
- const readOnly = context.organizationGraph?.status === 'connected';
250
- const origin = readOnly ? organizationWebOrigin(context.organizationGraph) : null;
251
- const sourceText = `組織のGraph(${context.organizationGraph?.server ?? '接続先不明'})を読み取り専用で表示しています。このMacのGraphではありません。`;
252
210
  return createView({
253
- ...(readOnly ? {
254
- canCorrect: false,
255
- readOnlyNote: origin ? organizationReadOnlyNoteLinked(context.document, origin, screenId) : ORGANIZATION_READ_ONLY_NOTE,
256
- sourceNotice: { label: '出典', text: origin ? organizationSourceText(context.document, origin, screenId, sourceText) : sourceText },
257
- } : {}),
258
211
  root: viewRoot,
259
212
  rail: context.rail,
260
213
  page: context.page,
@@ -334,7 +287,6 @@ function renderSource(doc, bar, status, onRecheck) {
334
287
  bar.append(
335
288
  fact('データ', data.data_dir),
336
289
  fact('Graph', graphLabel, data.graph.status === 'ready' ? '' : 'is-warning'),
337
- fact('組織のGraph', data.organization_graph?.status === 'connected' ? '読んでいます(読み取りのみ)' : '読んでいません'),
338
290
  );
339
291
  }
340
292
 
@@ -446,10 +398,8 @@ export function createLocalWebShell({
446
398
  slot.replaceChildren();
447
399
  const rail = rails.get(screen.id) ?? null;
448
400
  rail?.replaceChildren();
449
- const organizationGraph = status.phase === 'ready' ? status.data.organization_graph ?? null : null;
450
- const readsOrganization = organizationGraph?.status === 'connected' && (screen.id === 'projects' || screen.id === 'graph');
451
- const page = Object.freeze({ crumbs: Object.freeze(['あなたのBrainbase', screen.label]), source: readsOrganization ? '組織のGraph(読み取りのみ)' : screen.source ?? null });
452
- const view = screen.mount(slot, Object.freeze({ ...context, rail, page, initialEntityId: entityId, organizationGraph })) ?? true;
401
+ const page = Object.freeze({ crumbs: Object.freeze(['あなたのBrainbase', screen.label]), source: screen.source ?? null });
402
+ const view = screen.mount(slot, Object.freeze({ ...context, rail, page, initialEntityId: entityId })) ?? true;
453
403
  mounted.set(screen.id, view);
454
404
  if (entityId && screen.id === 'today' && typeof view?.openDecision === 'function') view.openDecision(entityId);
455
405
  if (entityId && screen.id === 'projects' && typeof view?.openProject === 'function') void view.openProject(entityId);