@unson/brainbase-mcp 0.23.5 → 0.23.7
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.
|
@@ -12,6 +12,14 @@ export interface CanonicalTaskContext extends JsonRecord {
|
|
|
12
12
|
/** Opaque host-policy scope, not authority or task input. */
|
|
13
13
|
storageScope?: string;
|
|
14
14
|
}
|
|
15
|
+
/** Conditions to review; this metadata is not evidence of completion. */
|
|
16
|
+
export interface CanonicalTaskCompletionContract {
|
|
17
|
+
goal: string;
|
|
18
|
+
criteria: Array<{
|
|
19
|
+
id: string;
|
|
20
|
+
condition: string;
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
15
23
|
export interface CanonicalTaskRecord extends JsonRecord {
|
|
16
24
|
id: string;
|
|
17
25
|
version: number;
|
|
@@ -29,11 +37,13 @@ export interface CanonicalTaskRecord extends JsonRecord {
|
|
|
29
37
|
project_codes?: string[];
|
|
30
38
|
/** What the work is for, in a few words (one per task); null when unlabelled. */
|
|
31
39
|
purpose_label?: string | null;
|
|
40
|
+
completion_contract?: CanonicalTaskCompletionContract | null;
|
|
32
41
|
created_at?: string | null;
|
|
33
42
|
updated_at?: string | null;
|
|
34
43
|
web_url?: string | null;
|
|
35
44
|
}
|
|
36
45
|
export interface CanonicalTaskCreateInput extends JsonRecord {
|
|
46
|
+
completion_contract?: unknown;
|
|
37
47
|
title: string;
|
|
38
48
|
description?: string | null;
|
|
39
49
|
priority?: CanonicalTaskPriority | string | null;
|
|
@@ -46,6 +56,7 @@ export interface CanonicalTaskCreateInput extends JsonRecord {
|
|
|
46
56
|
purpose_label?: string | null;
|
|
47
57
|
}
|
|
48
58
|
export interface CanonicalTaskUpdateInput extends JsonRecord {
|
|
59
|
+
completion_contract?: unknown;
|
|
49
60
|
title?: string;
|
|
50
61
|
description?: string | null;
|
|
51
62
|
priority?: CanonicalTaskPriority | string | null;
|
|
@@ -26,6 +26,7 @@ const DEFAULT_LIMIT = 20;
|
|
|
26
26
|
const MAX_LIMIT = 100;
|
|
27
27
|
const RESERVED_IDEMPOTENCY_PREFIXES = ['api:', 'workflow:'];
|
|
28
28
|
const CREATE_FIELDS = new Set([
|
|
29
|
+
'completion_contract',
|
|
29
30
|
'title',
|
|
30
31
|
'description',
|
|
31
32
|
'priority',
|
|
@@ -38,6 +39,7 @@ const CREATE_FIELDS = new Set([
|
|
|
38
39
|
'purpose_label',
|
|
39
40
|
]);
|
|
40
41
|
const MUTABLE_FIELDS = new Set([
|
|
42
|
+
'completion_contract',
|
|
41
43
|
'expected_version',
|
|
42
44
|
'title',
|
|
43
45
|
'description',
|
|
@@ -79,6 +81,39 @@ const MAX_PURPOSE_LABEL_LENGTH = 30;
|
|
|
79
81
|
function normalizePurposeLabel(value) {
|
|
80
82
|
return normalizeString(value, 'purpose_label', { max: MAX_PURPOSE_LABEL_LENGTH });
|
|
81
83
|
}
|
|
84
|
+
function normalizeCompletionContract(value) {
|
|
85
|
+
if (value == null)
|
|
86
|
+
return null;
|
|
87
|
+
const object = (candidate, keys) => {
|
|
88
|
+
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)
|
|
89
|
+
|| ![Object.prototype, null].includes(Object.getPrototypeOf(candidate))
|
|
90
|
+
|| Object.keys(candidate).some((key) => !keys.includes(key))) {
|
|
91
|
+
fail('validation_error', 'completion_contract contains an invalid object');
|
|
92
|
+
}
|
|
93
|
+
return candidate;
|
|
94
|
+
};
|
|
95
|
+
const text = (candidate, field) => {
|
|
96
|
+
if (typeof candidate !== 'string')
|
|
97
|
+
fail('validation_error', `${field} must be a string`);
|
|
98
|
+
return normalizeString(candidate, field, { required: true, max: 2000, multiline: true });
|
|
99
|
+
};
|
|
100
|
+
const contract = object(value, ['goal', 'criteria']);
|
|
101
|
+
const goal = text(contract.goal, 'completion_contract.goal');
|
|
102
|
+
if (!Array.isArray(contract.criteria) || contract.criteria.length < 1 || contract.criteria.length > 50) {
|
|
103
|
+
fail('validation_error', 'completion_contract.criteria must contain 1 to 50 conditions');
|
|
104
|
+
}
|
|
105
|
+
const ids = new Set();
|
|
106
|
+
const criteria = Array.from(contract.criteria, (candidate) => {
|
|
107
|
+
const criterion = object(candidate, ['id', 'condition']);
|
|
108
|
+
if (typeof criterion.id !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u.test(criterion.id)
|
|
109
|
+
|| ids.has(criterion.id)) {
|
|
110
|
+
fail('validation_error', 'completion_contract criterion id is invalid or duplicated');
|
|
111
|
+
}
|
|
112
|
+
ids.add(criterion.id);
|
|
113
|
+
return { id: criterion.id, condition: text(criterion.condition, 'completion_contract.criteria.condition') };
|
|
114
|
+
});
|
|
115
|
+
return { goal, criteria };
|
|
116
|
+
}
|
|
82
117
|
function normalizeIsoDate(value, field) {
|
|
83
118
|
const text = normalizeString(value, field);
|
|
84
119
|
if (text == null)
|
|
@@ -635,7 +670,9 @@ export class CanonicalTaskService {
|
|
|
635
670
|
if (!isCanonicalTaskPriority(priority))
|
|
636
671
|
fail('validation_error', 'priority is invalid', 400, { field: 'priority' });
|
|
637
672
|
const purposeLabel = normalizePurposeLabel(input.purpose_label);
|
|
673
|
+
const completionContract = normalizeCompletionContract(input.completion_contract);
|
|
638
674
|
return {
|
|
675
|
+
...(completionContract ? { completion_contract: completionContract } : {}),
|
|
639
676
|
title,
|
|
640
677
|
description: normalizeString(input.description, 'description', { max: 10000, multiline: true }),
|
|
641
678
|
priority,
|
|
@@ -656,6 +693,8 @@ export class CanonicalTaskService {
|
|
|
656
693
|
if (unknownFields.length > 0)
|
|
657
694
|
fail('validation_error', 'Task update contains unsupported fields', 400, { fields: unknownFields });
|
|
658
695
|
const patch = {};
|
|
696
|
+
if ('completion_contract' in input)
|
|
697
|
+
patch.completion_contract = normalizeCompletionContract(input.completion_contract);
|
|
659
698
|
if ('title' in input)
|
|
660
699
|
patch.title = normalizeString(input.title, 'title', { required: true, max: MAX_TITLE_LENGTH });
|
|
661
700
|
if ('description' in input)
|
|
@@ -857,6 +896,12 @@ export class CanonicalTaskService {
|
|
|
857
896
|
response.project_codes = [];
|
|
858
897
|
if (response.purpose_label === undefined)
|
|
859
898
|
response.purpose_label = null;
|
|
899
|
+
try {
|
|
900
|
+
response.completion_contract = normalizeCompletionContract(response.completion_contract);
|
|
901
|
+
}
|
|
902
|
+
catch {
|
|
903
|
+
fail('task_store_invalid', 'Stored Task completion contract is invalid', 503);
|
|
904
|
+
}
|
|
860
905
|
return response;
|
|
861
906
|
}
|
|
862
907
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.7",
|
|
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",
|
|
@@ -423,5 +423,5 @@
|
|
|
423
423
|
"engines": {
|
|
424
424
|
"node": ">=20"
|
|
425
425
|
},
|
|
426
|
-
"gitHead": "
|
|
426
|
+
"gitHead": "5dd3fb22d8a4b559ccb0e0ee14a6e9cdac6a4b4a"
|
|
427
427
|
}
|
package/ui/judgment-history.js
CHANGED
|
@@ -606,7 +606,9 @@ function aggregateNormalJudgmentHistory(home, { now = new Date(), period = PERIO
|
|
|
606
606
|
confirmedOutcomes: null,
|
|
607
607
|
},
|
|
608
608
|
visibleCount: 0,
|
|
609
|
-
|
|
609
|
+
retrievedCount: 0,
|
|
610
|
+
countComplete: false,
|
|
611
|
+
totalCount: home.status === 'available' && home.coverage?.complete === true ? nonNegativeInteger(home.coverage?.total) : null,
|
|
610
612
|
period: normalizedPeriod(period),
|
|
611
613
|
query: text(query),
|
|
612
614
|
status: home.status,
|
|
@@ -660,7 +662,10 @@ function aggregateNormalJudgmentHistory(home, { now = new Date(), period = PERIO
|
|
|
660
662
|
}
|
|
661
663
|
}
|
|
662
664
|
const metricsUnconfirmed = home.status === 'partial' && rows.length === 0;
|
|
663
|
-
|
|
665
|
+
// A page length is a retrieved count, never proof of the whole range.
|
|
666
|
+
const countComplete = home.status === 'available' && home.coverage?.complete === true
|
|
667
|
+
&& nonNegativeInteger(home.coverage?.total) !== null && !needle;
|
|
668
|
+
const judgments = countComplete ? home.coverage.total : null;
|
|
664
669
|
const referenceCount = metricsUnconfirmed || referencesUnconfirmed ? null : references.size;
|
|
665
670
|
const confirmedOutcomes = metricsUnconfirmed
|
|
666
671
|
? null
|
|
@@ -680,7 +685,9 @@ function aggregateNormalJudgmentHistory(home, { now = new Date(), period = PERIO
|
|
|
680
685
|
confirmedOutcomes,
|
|
681
686
|
},
|
|
682
687
|
visibleCount: rows.length,
|
|
683
|
-
|
|
688
|
+
retrievedCount: rows.length,
|
|
689
|
+
countComplete,
|
|
690
|
+
totalCount: home.status === 'available' && home.coverage?.complete === true ? nonNegativeInteger(home.coverage?.total) : null,
|
|
684
691
|
};
|
|
685
692
|
}
|
|
686
693
|
|
|
@@ -1328,7 +1335,7 @@ function paginationControls(doc, aggregate, onNext) {
|
|
|
1328
1335
|
const total = aggregate?.totalCount;
|
|
1329
1336
|
if (!pagination?.hasNext && total === null) return null;
|
|
1330
1337
|
const wrap = makeWorkspaceElement(doc, 'div', { className: 'bb-jh-pagination' });
|
|
1331
|
-
const count = total === null ? '同じ範囲の件数は未確認' :
|
|
1338
|
+
const count = total === null ? '同じ範囲の件数は未確認' : `${aggregate.query ? '検索前の全件数' : '同じ範囲の件数'}: ${total}件`;
|
|
1332
1339
|
wrap.append(makeWorkspaceElement(doc, 'span', { className: 'bb-jh-pagination-count', text: count }));
|
|
1333
1340
|
if (pagination?.hasNext) {
|
|
1334
1341
|
wrap.append(workspaceButton(doc, { text: '次のページ', variant: 'quiet', onClick: onNext, attrs: { 'data-action': 'next-page' } }));
|
|
@@ -1709,7 +1716,12 @@ export function createJudgmentHistoryUI({
|
|
|
1709
1716
|
const stats = aggregate.stats ?? {};
|
|
1710
1717
|
const metricCount = (value, suffix) => value === null || value === undefined ? '未確認' : `${value}${suffix}`;
|
|
1711
1718
|
wrapper.append(workspaceMetrics(doc, normalMode() ? [
|
|
1712
|
-
{
|
|
1719
|
+
{
|
|
1720
|
+
label: aggregate.query ? '表示対象' : aggregate.countComplete ? '判断件数' : '取得済み',
|
|
1721
|
+
value: metricCount(aggregate.countComplete ? stats.judgments : aggregate.retrievedCount, '件'),
|
|
1722
|
+
note: aggregate.countComplete ? 'この期間・絞り込みの全件数'
|
|
1723
|
+
: aggregate.query ? '取得済み履歴の検索結果・全件数未確認' : '全件数未確認',
|
|
1724
|
+
},
|
|
1713
1725
|
{ label: '使った参照', value: metricCount(stats.references, '件'), note: '当時の記録にある一意の参照' },
|
|
1714
1726
|
{ label: '確認済みの結果', value: metricCount(stats.confirmedOutcomes, '件'), note: '成果が確認済みと記録された判断' },
|
|
1715
1727
|
] : [
|
|
@@ -1717,8 +1729,8 @@ export function createJudgmentHistoryUI({
|
|
|
1717
1729
|
{ label: 'そこで使った参照', value: metricCount(stats.references, '件'), note: '代理判断に含まれる一意の参照' },
|
|
1718
1730
|
{ label: '判断回数にすると', value: metricCount(stats.equivalent, '回分相当'), note: '代理判断1件を1回分として表示' },
|
|
1719
1731
|
], { ariaLabel: '判断履歴の集計' }));
|
|
1720
|
-
const emptyText = state.home?.status === 'partial'
|
|
1721
|
-
? '
|
|
1732
|
+
const emptyText = state.home?.status === 'partial' || (normalMode() && !aggregate.countComplete)
|
|
1733
|
+
? 'この期間に取得できた判断履歴はありません。全件数が未確認のため0件とは確認できません。'
|
|
1722
1734
|
: 'この期間の判断履歴はありません。';
|
|
1723
1735
|
wrapper.append(workspaceLedger(doc, {
|
|
1724
1736
|
className: 'bb-jh-ledger',
|