@unson/brainbase-mcp 0.41.7 → 0.41.9

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.
@@ -55,6 +55,16 @@ const PHILOSOPHY_STATUS_LABELS = Object.freeze({
55
55
  archived: '終了',
56
56
  });
57
57
  const OPERATOR_LABELS = Object.freeze({ at_least: '以上', at_most: '以下', equals: '一致' });
58
+ const SOURCE_KIND_LABELS = Object.freeze({
59
+ candidate: '候補からの登録',
60
+ document: '文書',
61
+ observation: '観察',
62
+ decision: '決定',
63
+ import: '取り込み',
64
+ });
65
+ // A record without a name is said to have none; its id stays in the record details (P9).
66
+ const UNNAMED_RECORD = '名前の記録なし';
67
+ const UNNAMED_VARIABLE = '名前の記録がない変数';
58
68
 
59
69
  export class FoundationOverviewError extends Error {
60
70
  constructor(code, message) {
@@ -138,6 +148,22 @@ function validatePhilosophyRecords(value) {
138
148
  return value;
139
149
  }
140
150
 
151
+ function validateUnscopedPhilosophyRecords(value) {
152
+ if (!Array.isArray(value)) throw error('invalid_schema', '適用範囲が未記録の哲学の形式を確認できません。');
153
+ for (const [index, item] of value.entries()) {
154
+ if (!isObject(item)
155
+ || item.kind !== 'philosophy'
156
+ || !nonEmpty(item.id)
157
+ || !Number.isSafeInteger(item.version) || item.version < 1
158
+ || item.applicability_state !== 'unrecorded'
159
+ || !isObject(item.payload)
160
+ || !nonEmpty(item.payload.statement)) {
161
+ throw error('invalid_schema', `適用範囲が未記録の哲学の${index + 1}件目の形式が不正です。`);
162
+ }
163
+ }
164
+ return value;
165
+ }
166
+
141
167
  /** Validate the host contract without converting an unreadable response into an empty catalog. */
142
168
  export function normalizeFoundationCatalog(value, expectedScopeId) {
143
169
  if (!isObject(value) || value.contractVersion !== FOUNDATION_OVERVIEW_CONTRACT_VERSION) {
@@ -153,7 +179,10 @@ export function normalizeFoundationCatalog(value, expectedScopeId) {
153
179
  const knownReadableStatuses = new Set(['complete', 'ready', 'resolved', 'empty']);
154
180
  const unreadableStatus = (readStatus && !knownReadableStatuses.has(readStatus))
155
181
  || (catalogStatus && !knownReadableStatuses.has(catalogStatus));
156
- const collections = [value.objectives, value.variables, value.models, value.philosophies];
182
+ // Older hosts omit this field; then it stays unknown instead of becoming [].
183
+ const unscopedPhilosophies = value.philosophies_without_applicability === undefined
184
+ ? undefined : validateUnscopedPhilosophyRecords(value.philosophies_without_applicability);
185
+ const collections = [value.objectives, value.variables, value.models, value.philosophies, unscopedPhilosophies];
157
186
  const hasRecords = collections.some((records) => Array.isArray(records) && records.length > 0);
158
187
  const absenceConfirmed = value.absence_confirmed === true;
159
188
  if (unreadableStatus || (!hasRecords && ('absence_confirmed' in value || readStatus || catalogStatus) && !absenceConfirmed)) {
@@ -170,6 +199,7 @@ export function normalizeFoundationCatalog(value, expectedScopeId) {
170
199
  variables: value.variables,
171
200
  models: value.models,
172
201
  philosophies: value.philosophies,
202
+ ...(unscopedPhilosophies ? { philosophies_without_applicability: unscopedPhilosophies } : {}),
173
203
  ...(readStatus ? { read_status: readStatus } : {}),
174
204
  ...(catalogStatus ? { status: catalogStatus } : {}),
175
205
  ...(typeof value.absence_confirmed === 'boolean' ? { absence_confirmed: value.absence_confirmed } : {}),
@@ -225,12 +255,52 @@ const SCOPE_LABELS = Object.freeze({
225
255
  company: '組織',
226
256
  });
227
257
 
258
+ function formatValidity(from, until) {
259
+ if (!from && !until) return null;
260
+ if (!from) return `${formatDate(until)} まで`;
261
+ return `${formatDate(from)} から ${until ? `${formatDate(until)} まで` : '(終了日の記録なし)'}`;
262
+ }
263
+
264
+ /**
265
+ * A scope in words: `{ type, id }` names the project or organization, and a validity scope
266
+ * (`subjectIds`, `validFrom`, `validUntil`) becomes its period and the number of subjects. The
267
+ * subject ids themselves belong to the record details.
268
+ */
228
269
  function formatScope(value) {
229
270
  if (!isObject(value)) return formatStructuredValue(value);
230
271
  const type = formatAny(value.type);
231
272
  const id = formatAny(value.id);
232
273
  if (type && id) return `${SCOPE_LABELS[type] ?? type}: ${id}`;
233
- return formatStructuredValue(value);
274
+ const subjects = firstValue(value, 'subjectIds', 'subject_ids');
275
+ const period = formatValidity(firstValue(value, 'validFrom', 'valid_from'), firstValue(value, 'validUntil', 'valid_until'));
276
+ const parts = [
277
+ Array.isArray(subjects) && subjects.length > 0 ? `対象 ${subjects.length}件` : null,
278
+ period ? `適用期間: ${period}` : null,
279
+ ].filter(Boolean);
280
+ if (parts.length > 0) return parts.join(' / ');
281
+ return formatAny(firstValue(value, 'text', 'description', 'statement', 'label', 'name'))
282
+ ?? '読める形の記録がありません(記録の詳細を参照)';
283
+ }
284
+
285
+ /** Where a definition came from, as kinds of source; the source and evidence ids stay in the details. */
286
+ function formatProvenance(provenance) {
287
+ if (!Array.isArray(provenance) || provenance.length === 0) return null;
288
+ return provenance.map((item) => {
289
+ const kind = SOURCE_KIND_LABELS[item?.sourceKind] ?? '種類の記録なし';
290
+ const evidence = Array.isArray(item?.evidenceIds) ? item.evidenceIds.length : 0;
291
+ return evidence > 0 ? `${kind}(証拠${evidence}件)` : kind;
292
+ }).join('、');
293
+ }
294
+
295
+ /** Ids, revisions and digests of one record, for the "記録の詳細" disclosure only. */
296
+ function recordDetailsElement(doc, rows, summary = '記録の詳細') {
297
+ const details = makeElement(doc, 'details', { className: 'bb-fov-original' });
298
+ details.append(makeElement(doc, 'summary', { text: summary }), workspaceDefinition(doc, rows));
299
+ return details;
300
+ }
301
+
302
+ function referenceLabels(refs) {
303
+ return Array.isArray(refs) && refs.length > 0 ? refs.map(referenceLabel).join('、') : null;
234
304
  }
235
305
 
236
306
  function recordDefinition(record) { return isObject(record?.definition) ? record.definition : {}; }
@@ -265,6 +335,15 @@ function statusElement(doc, definition, className = '') {
265
335
  return makeElement(doc, 'p', { className: `bb-fov-status${className ? ` ${className}` : ''}`, text: statusText(definition) });
266
336
  }
267
337
 
338
+ /**
339
+ * The one state a reader needs in front of a model: whether it is held as verified or as a
340
+ * hypothesis. Adoption, validation and authorized uses stay in the record details.
341
+ */
342
+ function certaintyText(definition) {
343
+ const label = EPISTEMIC_LABELS[definition.epistemicState] ?? VALIDATION_LABELS[definition.validationState];
344
+ return `確からしさ: ${label ?? '記録なし'}`;
345
+ }
346
+
268
347
  function detailsFor(record) {
269
348
  return isObject(record?.details) ? record.details : {};
270
349
  }
@@ -310,9 +389,7 @@ function criteriaList(doc, definition, variableMap) {
310
389
  const ref = criterion?.variableRef;
311
390
  const exact = variableMap.get(foundationKey(ref));
312
391
  const exactDefinition = exact ? recordDefinition(exact) : null;
313
- const name = exactDefinition
314
- ? (formatAny(exactDefinition.meaning) ?? referenceLabel(ref))
315
- : referenceLabel(ref);
392
+ const name = (exactDefinition ? formatAny(exactDefinition.meaning) : null) ?? UNNAMED_VARIABLE;
316
393
  const variableDetails = exactDefinition
317
394
  ? [
318
395
  ['定義', exactDefinition.definition],
@@ -328,7 +405,7 @@ function criteriaList(doc, definition, variableMap) {
328
405
  const status = exact ? '参照確認済み' : '参照未確認(基準変数の版が一致しません)';
329
406
  list.append(makeElement(doc, 'li', {
330
407
  className: exact ? '' : 'is-unrecorded',
331
- text: `${name}${variableDetails.length > 0 ? `(${variableDetails.join(' / ')})` : ''}(${referenceLabel(ref)}) ${operator}${target === null ? '' : ` ${target}`} — ${status}`,
408
+ text: `${name}${variableDetails.length > 0 ? `(${variableDetails.join(' / ')})` : ''} ${operator}${target === null ? '' : ` ${target}`} — ${status}`,
332
409
  }));
333
410
  }
334
411
  return list;
@@ -395,7 +472,7 @@ function recordedStateElement(doc, record) {
395
472
  function objectiveCard(doc, record, variableMap) {
396
473
  const definition = recordDefinition(record);
397
474
  const details = detailsFor(record);
398
- const title = formatAny(firstValue(details, 'title')) ?? formatAny(definition.meaning) ?? definition.id;
475
+ const title = formatAny(firstValue(details, 'title')) ?? formatAny(definition.meaning) ?? UNNAMED_RECORD;
399
476
  const gaps = missingObjectiveFields(record);
400
477
  const current = currentStateFor(record);
401
478
  const evaluationPeriod = formatAny(firstValue(details, 'evaluation_period_note', 'evaluationPeriodNote'))
@@ -446,11 +523,16 @@ function objectiveReading(doc, record, variableMap, recordContexts) {
446
523
  const provenance = Array.isArray(definition.provenance) ? definition.provenance : [];
447
524
  section('出典', workspaceDefinition(doc, [
448
525
  ['記録元', recordContexts?.get(record)?.label ?? formatAny(record.source) ?? '未記録'],
449
- ['根拠', provenance.length ? formatStructuredValue(provenance) : '未記録'],
526
+ ['根拠', formatProvenance(provenance) ?? '未記録'],
450
527
  ]));
528
+ const criteriaRefs = Array.isArray(definition.criteria) ? definition.criteria.map((criterion) => criterion?.variableRef) : [];
451
529
  const original = makeElement(doc, 'details', { className: 'bb-fov-original' });
452
530
  original.append(makeElement(doc, 'summary', { text: '記録の詳細・参照・原文' }), objectiveCard(doc, record, variableMap),
453
- workspaceDefinition(doc, [['目的ID', definition.id], ['版', definition.revision], ['digest', record.digest]]));
531
+ workspaceDefinition(doc, [
532
+ ['目的ID', definition.id], ['版', definition.revision], ['digest', record.digest],
533
+ ['基準変数の参照', referenceLabels(criteriaRefs)],
534
+ ['根拠の記録', provenance.length ? formatStructuredValue(provenance) : null],
535
+ ]));
454
536
  body.append(original); return body;
455
537
  }
456
538
 
@@ -477,12 +559,12 @@ function renderObjectives(doc, catalog, viewState, callbacks, recordContexts) {
477
559
  const opener = doc.activeElement;
478
560
  viewState.dialog?.close();
479
561
  viewState.dialog = workspaceDialog(doc, {
480
- title: formatAny(details.title) ?? formatAny(definition.meaning) ?? definition.id, opener,
562
+ title: formatAny(details.title) ?? formatAny(definition.meaning) ?? UNNAMED_RECORD, opener,
481
563
  content: objectiveReading(doc, record, recordContexts?.get(record)?.variableMap ?? variableMap, recordContexts),
482
564
  });
483
565
  },
484
566
  cells: [
485
- { primary: formatAny(details.title) ?? formatAny(definition.meaning) ?? definition.id },
567
+ { primary: formatAny(details.title) ?? formatAny(definition.meaning) ?? UNNAMED_RECORD },
486
568
  current.text ?? current.status ?? '未記録',
487
569
  { text: current.missing.length ? current.missing.join('・') : missingObjectiveFields(record).join('・') || '記録なし', className: 'bb-fov-missing' },
488
570
  formatDate(current.asOf),
@@ -506,13 +588,13 @@ function philosophyApplicability(payload) {
506
588
  const period = validFrom
507
589
  ? `${formatDate(validFrom)}から${validUntil ? ` ${formatDate(validUntil)}まで` : ''}`
508
590
  : null;
509
- return [scope, period ? `適用期間: ${period}` : null].filter(Boolean).join(' / ') || formatStructuredValue(legacy);
591
+ return [scope, period ? `適用期間: ${period}` : null].filter(Boolean).join(' / ') || formatScope(legacy);
510
592
  }
511
593
 
512
594
  function philosophyCard(doc, record) {
513
595
  const payload = record.payload;
514
596
  const card = makeElement(doc, 'article', { className: 'bb-fov-philosophy-card' });
515
- const title = formatAny(payload.title) ?? record.id;
597
+ const title = formatAny(payload.title) ?? UNNAMED_RECORD;
516
598
  card.append(makeElement(doc, 'h3', { text: title }));
517
599
 
518
600
  if (formatAny(payload.statement)) card.append(makeElement(doc, 'p', { className: 'bb-fov-philosophy-statement', text: formatAny(payload.statement) }));
@@ -527,21 +609,53 @@ function philosophyCard(doc, record) {
527
609
  return card;
528
610
  }
529
611
 
612
+ function unscopedPhilosophyCard(doc, record, recordContexts) {
613
+ const payload = record.payload;
614
+ const card = makeElement(doc, 'article', { className: 'bb-fov-philosophy-card is-unscoped' });
615
+ card.append(makeElement(doc, 'h3', { text: formatAny(payload.title) ?? UNNAMED_RECORD }));
616
+ card.append(makeElement(doc, 'p', { className: 'bb-fov-philosophy-statement', text: payload.statement }));
617
+ if (formatAny(payload.summary) && payload.summary !== payload.statement) card.append(makeElement(doc, 'p', { text: payload.summary }));
618
+ // The section notice explains what an unrecorded applicability means.
619
+ card.append(workspaceDefinition(doc, [['適用範囲', '未記録']]));
620
+ const refs = makeElement(doc, 'details', { className: 'bb-fov-original' });
621
+ refs.append(makeElement(doc, 'summary', { text: '記録の詳細' }), workspaceDefinition(doc, [['出典', recordContexts?.get(record)?.label ?? '未記録'], ['状態', payload.status], ['Graphの版', record.version], ['記録ID', record.id]]));
622
+ card.append(refs);
623
+ return card;
624
+ }
625
+
530
626
  function renderPhilosophies(doc, catalog, recordContexts) {
531
627
  const section = makeElement(doc, 'section', { className: 'bb-fov-section', attrs: { 'aria-label': '哲学' } });
532
-
533
- if (catalog.philosophies.length === 0) {
534
- section.append(workspaceNotice(doc, { label: '哲学', text: 'この対象範囲には登録された哲学がありません。' }));
628
+ const unscoped = catalog.philosophies_without_applicability;
629
+
630
+ if (catalog.philosophies.length === 0 && !unscoped?.length) {
631
+ section.append(workspaceNotice(doc, {
632
+ label: '哲学',
633
+ text: unscoped
634
+ ? 'この対象範囲には登録された哲学がありません。'
635
+ // Without the unscoped list, only the verified list is known to be empty.
636
+ : '適用範囲が記録された哲学はありません。適用範囲が未記録の哲学があるかは確認できていません。',
637
+ }));
535
638
  return section;
536
639
  }
537
- const grid = makeElement(doc, 'div', { className: 'bb-fov-philosophy-grid' });
538
- for (const record of catalog.philosophies) {
539
- const card = philosophyCard(doc, record);
540
- const refs = makeElement(doc, 'details', { className: 'bb-fov-original' });
541
- refs.append(makeElement(doc, 'summary', { text: '記録の詳細' }), workspaceDefinition(doc, [['出典', recordContexts?.get(record)?.label ?? '未記録'], ['状態', record.payload.status], ['版', record.revision], ['記録ID', record.id], ['digest', record.digest]]));
542
- card.append(refs); grid.append(card);
640
+ if (catalog.philosophies.length) {
641
+ const grid = makeElement(doc, 'div', { className: 'bb-fov-philosophy-grid' });
642
+ for (const record of catalog.philosophies) {
643
+ const card = philosophyCard(doc, record);
644
+ const refs = makeElement(doc, 'details', { className: 'bb-fov-original' });
645
+ refs.append(makeElement(doc, 'summary', { text: '記録の詳細' }), workspaceDefinition(doc, [['出典', recordContexts?.get(record)?.label ?? '未記録'], ['状態', record.payload.status], ['版', record.revision], ['記録ID', record.id], ['digest', record.digest]]));
646
+ card.append(refs); grid.append(card);
647
+ }
648
+ section.append(grid);
649
+ }
650
+ if (unscoped?.length) {
651
+ section.append(workspaceNotice(doc, {
652
+ label: '適用範囲が未記録',
653
+ text: `次の${unscoped.length}件は、判断への適用範囲が記録されていない哲学です。判断では参照されますが、どの範囲に当てはまるかは確かめられていません。`,
654
+ }));
655
+ const grid = makeElement(doc, 'div', { className: 'bb-fov-philosophy-grid' });
656
+ for (const record of unscoped) grid.append(unscopedPhilosophyCard(doc, record, recordContexts));
657
+ section.append(grid);
543
658
  }
544
- section.append(grid);
545
659
  return section;
546
660
  }
547
661
 
@@ -569,10 +683,10 @@ function renderReferenceList(doc, refs, variableMap, label) {
569
683
  }
570
684
  for (const ref of refs) {
571
685
  const exact = variableMap.get(foundationKey(ref));
572
- const name = exact ? formatAny(recordDefinition(exact).meaning) ?? referenceLabel(ref) : referenceLabel(ref);
686
+ const name = (exact ? formatAny(recordDefinition(exact).meaning) : null) ?? UNNAMED_VARIABLE;
573
687
  list.append(makeElement(doc, 'li', {
574
688
  className: exact ? '' : 'is-unrecorded',
575
- text: exact ? `${name}(${referenceLabel(ref)})` : `${referenceLabel(ref)} — 未接続(変数の版が一致しません)`,
689
+ text: exact ? name : `${name} — 未接続(変数の版が一致しません)`,
576
690
  }));
577
691
  }
578
692
  return list;
@@ -581,16 +695,32 @@ function renderReferenceList(doc, refs, variableMap, label) {
581
695
  function modelCard(doc, record, variableMap) {
582
696
  const definition = recordDefinition(record);
583
697
  const card = makeElement(doc, 'article', { className: 'bb-fov-model-card' });
584
- card.append(makeElement(doc, 'h3', { text: formatAny(definition.meaning) ?? definition.id }));
585
- card.append(makeElement(doc, 'code', { className: 'bb-fov-record-id', text: `${definition.id}@${definition.revision}` }));
586
- card.append(statusElement(doc, definition));
698
+ // The stored scope is ids and a registration time, not the reader's "where it applies"; the
699
+ // record's own words go in front, and the stored scope waits in the details.
700
+ const applicability = definition.applicability ?? definition.scope;
701
+ const registered = isObject(applicability) ? applicability : {};
702
+ const subjects = firstValue(registered, 'subjectIds', 'subject_ids');
703
+ const applicabilityText = formatAny(firstValue(detailsFor(record), 'applicability_text', 'applicabilityText'))
704
+ ?? (isObject(applicability) ? null : formatAny(applicability));
705
+ card.append(makeElement(doc, 'h3', { text: formatAny(definition.meaning) ?? UNNAMED_RECORD }));
706
+ card.append(makeElement(doc, 'p', { className: 'bb-fov-status', text: certaintyText(definition) }));
587
707
  card.append(workspaceDefinition(doc, [
588
708
  ['関係', formatAny(definition.relationship)],
589
- ['適用範囲', formatAny(definition.applicability ?? definition.scope)],
709
+ ['適用範囲', applicabilityText ?? { text: '範囲の説明は記録されていません', className: 'is-unrecorded' }],
590
710
  ['不確かさ', formatAny(definition.uncertainty)],
591
711
  ['入力変数', renderReferenceList(doc, definition.inputVariableRefs, variableMap, '入力変数')],
592
712
  ['出力変数', renderReferenceList(doc, definition.outputVariableRefs, variableMap, '出力変数')],
593
713
  ]));
714
+ card.append(recordDetailsElement(doc, [
715
+ ['状態の内訳', statusText(definition)],
716
+ ['記録ID', definition.id],
717
+ ['版', definition.revision],
718
+ ['digest', record.digest],
719
+ ['登録上の対象', Array.isArray(subjects) && subjects.length > 0 ? subjects.join('、') : null],
720
+ ['登録上の開始日時', formatValidity(firstValue(registered, 'validFrom', 'valid_from'), firstValue(registered, 'validUntil', 'valid_until'))],
721
+ ['入力変数の参照', referenceLabels(definition.inputVariableRefs)],
722
+ ['出力変数の参照', referenceLabels(definition.outputVariableRefs)],
723
+ ]));
594
724
  return card;
595
725
  }
596
726
 
@@ -712,7 +842,7 @@ export function renderFoundationOverview(root, state, {
712
842
  surface.append(workspacePageHeader(doc, {
713
843
  crumbs: ['Brainbase', '目的と現状'],
714
844
  title: '目的と現状',
715
- lead: '選択したプロジェクトの目的、哲学、世界モデルを、登録状態と未確認の点を分けて読みます。',
845
+ lead: '選択したプロジェクトの目的、哲学、世界モデルを、記録された内容と未確認の点を分けて読みます。',
716
846
  source: `正本: ${scopeId}`,
717
847
  }));
718
848
  }
@@ -734,7 +864,7 @@ export function renderFoundationOverview(root, state, {
734
864
  return workspaceDisclosure(doc, { title, help, content, open: viewState[key] ?? initialOpen, onToggle: open => { viewState[key] = open; } });
735
865
  };
736
866
  surface.append(fold('哲学', '判断の前提となる考え方です。登録された本文と、判断への適用範囲を分けて表示します。', renderPhilosophies(doc, catalog, recordContexts), true));
737
- surface.append(fold('世界モデル', '何をすると結果が変わるか、という仮説。未検証・許可用途を分けて確認します。', renderModels(doc, catalog, viewState, callbacks, recordContexts), false));
867
+ surface.append(fold('世界モデル', '何をすると結果が変わるか、という仮説。検証済みか仮説かを先に示し、ほかの状態や記録の番号は各モデルの「記録の詳細」で確認できます。', renderModels(doc, catalog, viewState, callbacks, recordContexts), false));
738
868
  root.append(surface);
739
869
  renderLoadingState(doc, root, viewState, false, onLoading);
740
870
  return surface;
@@ -751,10 +881,11 @@ export function renderFoundationCollectionOverview(root, sources, {
751
881
  } = {}) {
752
882
  const doc = getDocument(explicitDocument);
753
883
  root.replaceChildren();
754
- const catalog = { objectives: [], variables: [], models: [], philosophies: [] };
884
+ const catalog = { objectives: [], variables: [], models: [], philosophies: [], philosophies_without_applicability: [] };
755
885
  const contexts = new WeakMap();
756
886
  const seen = new Map();
757
887
  let ready = 0;
888
+ let unscopedUnknown = false;
758
889
  for (const source of sources) {
759
890
  if (source.state.status !== 'ready') {
760
891
  if (source.state.status === 'loading') {
@@ -768,8 +899,9 @@ export function renderFoundationCollectionOverview(root, sources, {
768
899
  const value = normalizeFoundationCatalog(source.state.catalog, source.scopeId);
769
900
  ready++;
770
901
  const variableMap = new Map(value.variables.map((record) => [foundationKey(recordDefinition(record)), record]));
771
- for (const kind of ['objectives', 'models', 'philosophies']) {
772
- for (const original of value[kind]) {
902
+ if (!value.philosophies_without_applicability) unscopedUnknown = true;
903
+ for (const kind of ['objectives', 'models', 'philosophies', 'philosophies_without_applicability']) {
904
+ for (const original of value[kind] ?? []) {
773
905
  const definition = original.definition ?? original;
774
906
  const scope = original.scope ?? original.currentScope ?? definition.scope;
775
907
  // Scope-less/project records remain tied to their authenticated catalog.
@@ -790,6 +922,8 @@ export function renderFoundationCollectionOverview(root, sources, {
790
922
  }
791
923
  }
792
924
  }
925
+ // A host that did not report unscoped philosophies leaves the absence unknown.
926
+ if (unscopedUnknown && catalog.philosophies_without_applicability.length === 0) delete catalog.philosophies_without_applicability;
793
927
  if (ready) {
794
928
  const mount = makeElement(doc, 'div');
795
929
  renderFoundationOverview(mount, { status: 'ready', catalog }, {
@@ -76,12 +76,25 @@ export function graphOwnShareSource(option, { fetcher } = {}) {
76
76
  });
77
77
  }
78
78
 
79
- /** What the owner sent: the snapshot ID, where it is kept and when it was sent. */
79
+ /** What the owner sent, in words: when it was sent. The snapshot ID and digest are in ownShareHandoverRows. */
80
80
  export function ownShareHandoverText(handover) {
81
81
  if (!isRecord(handover) || !textOrNull(handover.graphId)) return null;
82
- const where = textOrNull(handover.projectCode);
83
- const digest = textOrNull(handover.digest) ?? '未確認';
84
- return `束 ${handover.graphId}(${where ? `置き場所 ${where}、` : ''}digest ${digest}、${formatDateTime(handover.createdAt)} に送信)`;
82
+ return `${formatDateTime(handover.createdAt)} に手元のOSSから送った束`;
83
+ }
84
+
85
+ /** The snapshot ID, where it is kept and its digest, for the record details only. */
86
+ export function ownShareHandoverRows(handover) {
87
+ if (!isRecord(handover) || !textOrNull(handover.graphId)) return null;
88
+ return [
89
+ ['束ID', handover.graphId],
90
+ ['置き場所', textOrNull(handover.projectCode)],
91
+ ['digest', textOrNull(handover.digest) ?? '未確認'],
92
+ ];
93
+ }
94
+
95
+ function handoverDetails(doc, handover) {
96
+ const rows = ownShareHandoverRows(handover);
97
+ return rows ? recordDetails(doc, rows) : null;
85
98
  }
86
99
 
87
100
  /**
@@ -113,6 +126,8 @@ export function ownShareSection(doc, { label, lead = OWN_LEAD, handover, childre
113
126
  const sent = ownShareHandoverText(handover);
114
127
  if (sent) facts.append(makeElement(doc, 'span', { text: sent }));
115
128
  section.append(facts);
129
+ const sentDetails = handoverDetails(doc, handover);
130
+ if (sentDetails) section.append(sentDetails);
116
131
  for (const child of children) if (child) section.append(child);
117
132
  return section;
118
133
  }
@@ -250,7 +265,7 @@ function personText(person) {
250
265
  }
251
266
 
252
267
  function criterionText(criterion) {
253
- const variable = textOrNull(criterion.variable?.meaning) ?? criterion.variable?.id ?? '変数';
268
+ const variable = textOrNull(criterion.variable?.meaning) ?? '名前の記録がない変数';
254
269
  if (criterion.target === null || criterion.target === undefined) return `${variable}(目標値なし)`;
255
270
  const value = `${String(criterion.target)}${textOrNull(criterion.variable?.unit) ? ` ${criterion.variable.unit}` : ''}`;
256
271
  if (criterion.operator === 'at_least') return `${variable}: ${value}以上`;
@@ -295,7 +310,7 @@ export function createOwnObjectivesView({
295
310
  function detail(objective) {
296
311
  const wrap = makeElement(doc, 'div', { className: 'bb-graph-own-detail', attrs: { 'aria-label': '選んだ目的(引き継いだ自分の分)' } });
297
312
  wrap.append(
298
- workspaceRailHead(doc, { kicker: '目的(引き継いだ自分の分)', title: objective.meaning, sub: objective.id }),
313
+ workspaceRailHead(doc, { kicker: '目的(引き継いだ自分の分)', title: objective.meaning }),
299
314
  closeButton(doc, () => controller.select(null)),
300
315
  );
301
316
  const criteria = makeElement(doc, 'ul', { className: 'bb-graph-plain-list' });
@@ -312,7 +327,11 @@ export function createOwnObjectivesView({
312
327
  ['状態', ownLabel(ADOPTION_LABELS, objective.adoptionState) ?? textOrNull(objective.adoptionState)],
313
328
  ['公開範囲(手元での設定)', ownLabel(VISIBILITY_LABELS, objective.visibility) ?? textOrNull(objective.visibility)],
314
329
  ]),
315
- recordDetails(doc, [['目的ID', objective.id], ['版', objective.revision]]),
330
+ recordDetails(doc, [
331
+ ['目的ID', objective.id],
332
+ ['版', objective.revision],
333
+ ['基準変数のID', objective.criteria.map((criterion) => textOrNull(criterion.variable?.id)).filter(Boolean).join('、') || null],
334
+ ]),
316
335
  ],
317
336
  }));
318
337
  return wrap;
@@ -344,7 +363,7 @@ export function createOwnObjectivesView({
344
363
  selected: objective.id === state.selectedId,
345
364
  onSelect: (id) => controller.select(id),
346
365
  cells: [
347
- { primary: objective.meaning, secondary: objective.id },
366
+ { primary: objective.meaning },
348
367
  ownLabel(ADOPTION_LABELS, objective.adoptionState) ?? String(objective.adoptionState ?? ''),
349
368
  periodText(objective.evaluationPeriod),
350
369
  personText(objective.accountable) ?? { text: '未登録', className: 'is-unresolved' },
@@ -365,7 +384,7 @@ export function createOwnObjectivesView({
365
384
  if (compact) {
366
385
  const facts = makeElement(doc, 'p', { className: 'bb-graph-own-facts', text: '読み取りのみ' });
367
386
  const refs = makeElement(doc, 'details');
368
- refs.append(makeElement(doc, 'summary', { text: '出典' }), makeElement(doc, 'p', { text: ownShareHandoverText(handover) ?? (state.list.state === 'not_handed_over' ? 'まだ引き継いでいません。' : '出典は確認できません。') }));
387
+ refs.append(makeElement(doc, 'summary', { text: '出典' }), makeElement(doc, 'p', { text: ownShareHandoverText(handover) ?? (state.list.state === 'not_handed_over' ? 'まだ引き継いでいません。' : '出典は確認できません。') }), handoverDetails(doc, handover));
369
388
  root.replaceChildren(facts, ...body(), refs);
370
389
  }
371
390
  else root.replaceChildren(ownShareSection(doc, { label: source.label, lead: OBJECTIVES_LEAD, handover, children: body(), className: 'bb-graph-own-objectives' }));
@@ -287,6 +287,7 @@
287
287
  -webkit-line-clamp: 2;
288
288
  }
289
289
  .knowledge-outcome .knowledge-row-meta { display: flex; flex-wrap: wrap; gap: .15rem .55rem; min-width: 0; }
290
+ .knowledge-outcome .knowledge-row-project { color: var(--knowledge-ink); font-size: .68rem; font-weight: 600; overflow-wrap: anywhere; }
290
291
  .knowledge-outcome .knowledge-row-type,
291
292
  .knowledge-outcome .knowledge-row-scope,
292
293
  .knowledge-outcome .knowledge-row-state { color: var(--knowledge-muted); font-size: .68rem; white-space: nowrap; }
@@ -451,3 +452,20 @@
451
452
  .knowledge-outcome .knowledge-skeleton-line { animation: none; }
452
453
  .knowledge-outcome .knowledge-button { transition: none; }
453
454
  }
455
+
456
+ .knowledge-outcome .knowledge-project-filter {
457
+ display: grid;
458
+ gap: .25rem;
459
+ min-width: min(16rem, 100%);
460
+ color: var(--knowledge-muted);
461
+ font-size: .72rem;
462
+ }
463
+ .knowledge-outcome .knowledge-list-truncated { margin-top: .25rem; color: var(--knowledge-muted); font-size: .72rem; }
464
+ .knowledge-outcome .knowledge-overview-owner {
465
+ display: grid;
466
+ gap: .5rem;
467
+ margin-top: .75rem;
468
+ border-top: 1px solid var(--knowledge-line);
469
+ padding-top: .75rem;
470
+ }
471
+ .knowledge-outcome .knowledge-overview-project { color: var(--knowledge-muted); font-size: .78rem; }