@unson/brainbase-mcp 0.23.6 → 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
|
}
|