@unson/brainbase-mcp 0.2.4 → 0.3.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/dist/tools.js CHANGED
@@ -1,14 +1,16 @@
1
+ import { isActiveAt } from './canonical-graph.js';
1
2
  import { buildOperationalizationPlan } from './operationalization.js';
2
- export function getContext(os) {
3
+ import { getCanonicalRelation } from './relation-registry.js';
4
+ export function getContext(os, options = {}) {
3
5
  const selfEntries = os.personalKg.filter((entry) => entry.type === 'self' || entry.type === 'value' || entry.type === 'judgment');
4
6
  const workEntries = os.personalKg.filter((entry) => entry.type === 'work' || entry.type === 'experience');
5
- const projects = os.graph.entities.filter((entity) => entity.type === 'project').map((entity) => ({
6
- id: entity.id,
7
- name: entity.name,
8
- summary: entity.summary,
9
- goal: readMetadataString(entity.metadata, 'goal'),
10
- status: readMetadataString(entity.metadata, 'status'),
11
- role: readMetadataString(entity.metadata, 'role'),
7
+ const scoped = scopedRecords(os, options);
8
+ const visibleIds = os.graph.version === 2 ? new Set(scoped.records.map((record) => record.entity.id)) : undefined;
9
+ const projects = os.graph.entities
10
+ .filter((entity) => entity.type === 'project' && (!visibleIds || visibleIds.has(entity.id)))
11
+ .map((entity) => ({
12
+ id: entity.id, name: entity.name, summary: entity.summary,
13
+ goal: readMetadataString(entity.metadata, 'goal'), status: readMetadataString(entity.metadata, 'status'), role: readMetadataString(entity.metadata, 'role'),
12
14
  sources: Array.isArray(entity.metadata?.sources) ? entity.metadata.sources : [],
13
15
  taskSources: Array.isArray(entity.metadata?.taskSources) ? entity.metadata.taskSources : [],
14
16
  decisionPrinciples: Array.isArray(entity.metadata?.decisionPrinciples) ? entity.metadata.decisionPrinciples : []
@@ -17,175 +19,210 @@ export function getContext(os) {
17
19
  owner: os.graph.owner ?? {},
18
20
  self: selfEntries.map((entry) => entry.text),
19
21
  projects,
20
- work: [
21
- ...projects.map((project) => project.summary ? `${project.name}: ${project.summary}` : project.name),
22
- ...workEntries.map((entry) => entry.text)
23
- ],
24
- relationships: os.relationships.relationships.map((relationship) => ({
25
- person: relationship.person,
26
- role: relationship.role,
27
- context: relationship.context
28
- })),
29
- decisions: os.decisions.map((decision) => ({
30
- title: decision.title,
31
- decision: decision.decision,
32
- rationale: decision.rationale
33
- })),
22
+ work: [...projects.map((project) => project.summary ? `${project.name}: ${project.summary}` : project.name), ...workEntries.map((entry) => entry.text)],
23
+ relationships: os.relationships.relationships.map((relationship) => ({ person: relationship.person, role: relationship.role, context: relationship.context })),
24
+ decisions: os.decisions.map((decision) => ({ title: decision.title, decision: decision.decision, rationale: decision.rationale })),
34
25
  canonicalFiles: ['graph.json', 'personal-kg.jsonl', 'relationships.json', 'decisions.jsonl'],
35
- note: 'Canonical local SSOT data is preferred over raw sources.'
26
+ note: 'Canonical local SSOT data is preferred over raw sources.',
27
+ ...(os.graph.version === 2 ? { canonicalGraph: {
28
+ schemaVersion: 2,
29
+ ontology: os.graph.ontology,
30
+ authority: 'local_graph',
31
+ asOf: scoped.asOf,
32
+ project: scoped.project ? { id: scoped.project.id, name: scoped.project.name } : null,
33
+ records: scoped.records.map(({ entity, relationPath }) => ({
34
+ ...entity, canonicalEntityId: entity.id, recordClass: 'canonical', relationPath, authority: 'local_graph'
35
+ }))
36
+ } } : {})
36
37
  };
37
38
  }
38
39
  export function listEntities(os, type) {
39
40
  const graphEntities = os.graph.entities.filter((entity) => !type || entity.type === type);
40
- const relationshipEntities = type && type !== 'relationship'
41
- ? []
42
- : os.relationships.relationships.map((relationship) => ({
43
- id: relationship.id,
44
- type: 'relationship',
45
- name: relationship.person,
46
- summary: relationship.context,
47
- tags: relationship.tags
48
- }));
49
- const decisionEntities = type && type !== 'decision'
50
- ? []
51
- : os.decisions.map((decision) => ({
52
- id: decision.id,
53
- type: 'decision',
54
- name: decision.title,
55
- summary: decision.decision,
56
- tags: decision.tags
57
- }));
58
- return {
59
- entities: [...graphEntities, ...relationshipEntities, ...decisionEntities]
60
- };
41
+ const relationshipEntities = type && type !== 'relationship' ? [] : os.relationships.relationships.map((relationship) => ({
42
+ id: relationship.id, type: 'relationship', name: relationship.person, summary: relationship.context, tags: relationship.tags
43
+ }));
44
+ const decisionEntities = type && type !== 'decision' ? [] : os.decisions.map((decision) => ({
45
+ id: decision.id, type: 'decision', name: decision.title, summary: decision.decision, tags: decision.tags
46
+ }));
47
+ return { entities: [...graphEntities, ...relationshipEntities, ...decisionEntities] };
61
48
  }
62
49
  export function searchPersonalKg(os, query, limit = 10) {
63
50
  return rank(os.personalKg.map((entry) => ({
64
- source: 'personal-kg',
65
- id: entry.id,
66
- title: entry.type,
67
- text: entry.text,
68
- score: scoreText(query, [entry.id, entry.type, entry.text, ...(entry.tags ?? [])])
51
+ source: 'personal-kg', id: entry.id, title: entry.type, text: entry.text,
52
+ score: scoreText(query, [entry.id, entry.type, entry.text, ...(entry.tags ?? [])]),
53
+ recordClass: 'unresolved', authority: 'personal_kg'
69
54
  })), limit);
70
55
  }
71
- export function searchAll(os, query, limit = 10) {
72
- const graphResults = os.graph.entities.map((entity) => ({
73
- source: 'graph',
74
- id: entity.id,
75
- title: entity.name,
56
+ export function searchAll(os, query, limit = 10, options = {}) {
57
+ const scoped = scopedRecords(os, options);
58
+ const graphRecords = os.graph.version === 2
59
+ ? scoped.records
60
+ : os.graph.entities.map((entity) => ({ entity: entity, relationPath: [] }));
61
+ const graphResults = graphRecords.map(({ entity, relationPath }) => ({
62
+ source: 'graph', id: entity.id, title: entity.name,
76
63
  text: [entity.summary ?? entity.name, metadataText(entity.metadata)].filter(Boolean).join('\n'),
77
- score: scoreText(query, [entity.id, entity.type, entity.name, entity.summary ?? '', metadataText(entity.metadata), ...(entity.tags ?? [])])
64
+ score: scoreText(query, [entity.id, entity.type, entity.name, ...(entity.aliases ?? []), entity.summary ?? '', metadataText(entity.metadata), ...(entity.tags ?? [])]),
65
+ canonicalEntityId: entity.id, recordClass: 'canonical', relationPath, authority: 'local_graph'
78
66
  }));
79
- const relationshipResults = os.relationships.relationships.map((relationship) => ({
80
- source: 'relationships',
81
- id: relationship.id,
82
- title: relationship.person,
83
- text: relationship.context,
84
- score: scoreText(query, [relationship.id, relationship.person, relationship.role ?? '', relationship.context, ...(relationship.tags ?? [])])
85
- }));
86
- const decisionResults = os.decisions.map((decision) => ({
87
- source: 'decisions',
88
- id: decision.id,
89
- title: decision.title,
90
- text: [decision.decision, decision.rationale ?? ''].filter(Boolean).join('\n'),
91
- score: scoreText(query, [decision.id, decision.title, decision.decision, decision.rationale ?? '', ...(decision.tags ?? [])])
92
- }));
93
- return rank([...graphResults, ...searchPersonalKg(os, query, limit), ...relationshipResults, ...decisionResults], limit);
67
+ const visibleIds = new Set(scoped.records.map((record) => record.entity.id));
68
+ const targets = os.graph.version === 2 ? projectionTargets(os.graph, scoped.asOf, visibleIds) : undefined;
69
+ const relationshipResults = os.relationships.relationships.map((relationship) => {
70
+ const projectionOf = targets?.person.get(normalize(relationship.person));
71
+ return {
72
+ source: 'relationships', id: relationship.id, title: relationship.person, text: relationship.context,
73
+ score: scoreText(query, [relationship.id, relationship.person, relationship.role ?? '', relationship.context, ...(relationship.tags ?? [])]),
74
+ ...(projectionOf ? { canonicalEntityId: projectionOf, projectionOf } : {}),
75
+ recordClass: projectionOf ? 'projection' : 'unresolved', authority: 'legacy_relationships'
76
+ };
77
+ });
78
+ const decisionResults = os.decisions.map((decision) => {
79
+ const projectionOf = targets?.decision.get(decision.id) ?? targets?.decision.get(normalize(decision.title));
80
+ return {
81
+ source: 'decisions', id: decision.id, title: decision.title,
82
+ text: [decision.decision, decision.rationale ?? ''].filter(Boolean).join('\n'),
83
+ score: scoreText(query, [decision.id, decision.title, decision.decision, decision.rationale ?? '', ...(decision.tags ?? [])]),
84
+ ...(projectionOf ? { canonicalEntityId: projectionOf, projectionOf } : {}),
85
+ recordClass: projectionOf ? 'projection' : 'unresolved', authority: 'legacy_decisions'
86
+ };
87
+ });
88
+ const additional = [...relationshipResults, ...decisionResults, ...(options.project ? [] : searchPersonalKg(os, query, limit))]
89
+ .filter((result) => !options.project || Boolean(result.projectionOf));
90
+ const canonicalById = new Map(graphResults.map((result) => [result.id, result]));
91
+ const retained = [];
92
+ for (const result of additional) {
93
+ const canonical = result.projectionOf ? canonicalById.get(result.projectionOf) : undefined;
94
+ if (canonical) {
95
+ canonical.score = Math.max(canonical.score, result.score);
96
+ if (result.source === 'relationships' || result.source === 'decisions') {
97
+ canonical.projectionSources = [...new Set([...(canonical.projectionSources ?? []), result.source])];
98
+ }
99
+ }
100
+ else
101
+ retained.push(result);
102
+ }
103
+ return rank([...graphResults, ...retained], limit);
94
104
  }
95
105
  export function onboardingStatus(os) {
96
106
  const seeded = {
97
107
  self: Boolean(os.graph.owner?.name) || os.personalKg.some((entry) => entry.type === 'self'),
98
108
  work: os.graph.entities.some((entity) => entity.type === 'project') || os.personalKg.some((entry) => entry.type === 'work'),
99
- relationships: os.relationships.relationships.length > 0
109
+ relationships: os.relationships.relationships.length > 0 || (os.graph.version === 2 && os.graph.edges.length > 0)
100
110
  };
101
- const missing = Object.entries(seeded)
102
- .filter(([, value]) => !value)
103
- .map(([key]) => key);
111
+ const missing = Object.entries(seeded).filter(([, value]) => !value).map(([key]) => key);
104
112
  const valueDemoReady = missing.length === 0;
105
- const operationalization = buildOperationalizationPlan({
106
- dataDir: os.dataDir,
107
- firstValueReady: valueDemoReady
108
- });
113
+ const operationalization = buildOperationalizationPlan({ dataDir: os.dataDir, firstValueReady: valueDemoReady });
109
114
  return {
110
- dataDir: os.dataDir,
111
- localBackend: {
112
- connected: true,
113
- backend: 'local'
114
- },
115
- agentMcp: {
116
- status: 'not_verified',
117
- verification: 'MCP設定を反映した新しいエージェントセッションで get_context / search を確認してください。'
118
- },
119
- operationallyReady: false,
120
- seeded,
121
- missing,
122
- valueDemo: {
123
- scope: 'local_cli_sample',
124
- ready: valueDemoReady,
125
- missing,
126
- command: `brainbase onboard:demo --dir ${shellArg(os.dataDir)} --scenario "<real request>"`,
127
- completionSignal: valueDemoReady ? 'cli_sample_ready' : 'needs_seed'
128
- },
115
+ dataDir: os.dataDir, localBackend: { connected: true, backend: 'local' },
116
+ agentMcp: { status: 'not_verified', verification: 'MCP設定を反映した新しいエージェントセッションで resolve_entity / get_context / search を確認してください。' },
117
+ operationallyReady: false, seeded, missing,
118
+ valueDemo: { scope: 'local_cli_sample', ready: valueDemoReady, missing, command: `brainbase onboard:demo --dir ${shellArg(os.dataDir)} --scenario "<real request>"`, completionSignal: valueDemoReady ? 'cli_sample_ready' : 'needs_seed' },
129
119
  operationalization,
130
- counts: {
131
- graphEntities: os.graph.entities.length,
132
- personalKgEntries: os.personalKg.length,
133
- relationships: os.relationships.relationships.length,
134
- decisions: os.decisions.length,
135
- rawSources: os.sourceCount
136
- }
120
+ counts: { graphEntities: os.graph.entities.length, graphEdges: os.graph.version === 2 ? os.graph.edges.length : 0, personalKgEntries: os.personalKg.length, relationships: os.relationships.relationships.length, decisions: os.decisions.length, rawSources: os.sourceCount }
121
+ };
122
+ }
123
+ function scopedRecords(os, options) {
124
+ const asOf = options.asOf ?? new Date().toISOString();
125
+ if (os.graph.version !== 2)
126
+ return { records: [], asOf };
127
+ const graph = os.graph;
128
+ const active = graph.entities.filter((entity) => isActiveAt(entity, asOf));
129
+ const activeIds = new Set(active.map((entity) => entity.id));
130
+ const project = options.project ? resolveProject(active, options.project) : undefined;
131
+ if (options.project && !project)
132
+ return { records: [], asOf };
133
+ if (!project)
134
+ return { records: active.map((entity) => ({ entity, relationPath: [] })), asOf };
135
+ const edges = graph.edges.filter((edge) => isActiveAt(edge, asOf) && activeIds.has(edge.fromId) && activeIds.has(edge.toId));
136
+ return {
137
+ records: active.flatMap((entity) => {
138
+ const relationPath = pathToProject(entity.id, project.id, edges);
139
+ return relationPath === undefined ? [] : [{ entity, relationPath }];
140
+ }), project, asOf
137
141
  };
138
142
  }
139
- function shellArg(value) {
140
- return /^[A-Za-z0-9_./:@%+=,-]+$/.test(value) ? value : JSON.stringify(value);
143
+ function pathToProject(entityId, projectId, edges) {
144
+ if (entityId === projectId)
145
+ return [];
146
+ const queue = [{ id: entityId, path: [] }];
147
+ const visited = new Set([entityId]);
148
+ while (queue.length > 0) {
149
+ const current = queue.shift();
150
+ for (const edge of edges) {
151
+ const relation = getCanonicalRelation(edge.relation);
152
+ if (relation.scopeTraversal === 'none' || relation.traversalDirection === 'none')
153
+ continue;
154
+ const next = relation.traversalDirection === 'forward' ? (edge.fromId === current.id ? edge.toId : undefined) : (edge.toId === current.id ? edge.fromId : undefined);
155
+ if (!next)
156
+ continue;
157
+ const path = [...current.path, edge.id];
158
+ if (next === projectId)
159
+ return path;
160
+ if (relation.scopeTraversal === 'project_transitive' && !visited.has(next)) {
161
+ visited.add(next);
162
+ queue.push({ id: next, path });
163
+ }
164
+ }
165
+ }
166
+ return undefined;
167
+ }
168
+ function resolveProject(entities, value) {
169
+ const needle = normalize(value);
170
+ const matches = entities.filter((entity) => entity.type === 'project' && [entity.id, entity.name, ...(entity.aliases ?? [])].some((candidate) => normalize(candidate) === needle));
171
+ return matches.length === 1 ? matches[0] : undefined;
172
+ }
173
+ function projectionTargets(graph, asOf, visibleIds) {
174
+ const person = new Map();
175
+ const decision = new Map();
176
+ const ambiguous = new Set();
177
+ for (const entity of graph.entities.filter((candidate) => visibleIds.has(candidate.id) && isActiveAt(candidate, asOf))) {
178
+ const target = entity.type === 'person' ? person : entity.type === 'decision' ? decision : undefined;
179
+ if (!target)
180
+ continue;
181
+ for (const value of [entity.id, entity.name, ...(entity.aliases ?? [])]) {
182
+ const key = value === entity.id ? value : normalize(value);
183
+ if (target.has(key) && target.get(key) !== entity.id)
184
+ ambiguous.add(`${entity.type}:${key}`);
185
+ else
186
+ target.set(key, entity.id);
187
+ }
188
+ }
189
+ for (const marker of ambiguous) {
190
+ const [kind, ...parts] = marker.split(':');
191
+ (kind === 'person' ? person : decision).delete(parts.join(':'));
192
+ }
193
+ return { person, decision };
141
194
  }
142
195
  function rank(results, limit) {
143
- return results
144
- .filter((result) => result.score > 0)
145
- .sort((a, b) => b.score - a.score || a.title.localeCompare(b.title))
196
+ const priority = { canonical: 0, projection: 1, unresolved: 2 };
197
+ return results.filter((result) => result.score > 0)
198
+ .sort((a, b) => b.score - a.score || priority[a.recordClass] - priority[b.recordClass] || a.title.localeCompare(b.title))
146
199
  .slice(0, Math.max(1, limit));
147
200
  }
148
201
  function scoreText(query, fields) {
149
- const terms = searchTokens(query);
150
- if (terms.length === 0) {
151
- return 0;
152
- }
153
- const haystack = fields.join('\n').toLowerCase();
154
- const phrase = query.trim().toLowerCase();
155
- const matchedTerms = terms.filter((term) => haystack.includes(term));
156
- if (phrase && haystack.includes(phrase)) {
157
- return terms.length + 20;
202
+ let best = 0;
203
+ for (const variant of queryAliases(query)) {
204
+ const terms = searchTokens(variant);
205
+ if (terms.length === 0)
206
+ continue;
207
+ const haystack = fields.join('\n').normalize('NFKC').toLowerCase();
208
+ const phrase = variant.trim().normalize('NFKC').toLowerCase();
209
+ const matched = terms.filter((term) => haystack.includes(term));
210
+ best = Math.max(best, phrase && haystack.includes(phrase) ? terms.length + 20 : matched.length === terms.length ? terms.length + 10 : matched.length);
158
211
  }
159
- if (matchedTerms.length === terms.length) {
160
- return terms.length + 10;
161
- }
162
- return matchedTerms.length;
212
+ return best;
213
+ }
214
+ function queryAliases(query) {
215
+ const canonical = query.normalize('NFKC').trim();
216
+ const alias = canonical.replace(/(?:さん|様|氏|くん|君|ちゃん)$/u, '').trim();
217
+ return alias && alias !== canonical ? [canonical, alias] : [canonical];
163
218
  }
164
219
  function searchTokens(query) {
165
220
  const seen = new Set();
166
- return query
167
- .normalize('NFKC')
168
- .replace(/[|/\\,;:()[\]{}"'`“”‘’、。・・,.:;()[]{}「」『』【】]/g, ' ')
169
- .replace(/[-_]+/g, ' ')
170
- .toLowerCase()
171
- .split(/\s+/)
172
- .map((term) => term.trim())
173
- .filter(Boolean)
174
- .filter((term) => {
175
- if (seen.has(term)) {
176
- return false;
177
- }
178
- seen.add(term);
179
- return true;
180
- });
181
- }
182
- function metadataText(metadata) {
183
- if (!metadata) {
184
- return '';
185
- }
186
- return JSON.stringify(metadata);
187
- }
188
- function readMetadataString(metadata, key) {
189
- const value = metadata?.[key];
190
- return typeof value === 'string' ? value : undefined;
221
+ return query.normalize('NFKC').replace(/[|/\\,;:()[\]{}"'`“”‘’、。・・,.:;()[]{}「」『』【】]/g, ' ')
222
+ .replace(/[-_]+/g, ' ').toLowerCase().split(/\s+/).map((term) => term.trim()).filter(Boolean)
223
+ .filter((term) => seen.has(term) ? false : (seen.add(term), true));
191
224
  }
225
+ function normalize(value) { return value.normalize('NFKC').replace(/\s+/g, '').toLowerCase(); }
226
+ function metadataText(metadata) { return metadata ? JSON.stringify(metadata) : ''; }
227
+ function readMetadataString(metadata, key) { const value = metadata?.[key]; return typeof value === 'string' ? value : undefined; }
228
+ function shellArg(value) { return /^[A-Za-z0-9_./:@%+=,-]+$/.test(value) ? value : JSON.stringify(value); }
package/dist/types.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export type EntityKind = 'person' | 'org' | 'project' | 'relationship' | 'decision';
2
+ export type CanonicalEntityKind = 'person' | 'org' | 'project' | 'decision';
3
+ export type CoreRelation = 'member_of' | 'participates_in' | 'accountable_for' | 'owned_by' | 'governs' | 'supersedes';
2
4
  export interface GraphEntity {
3
5
  id: string;
4
6
  type: Exclude<EntityKind, 'decision'>;
@@ -7,7 +9,7 @@ export interface GraphEntity {
7
9
  tags?: string[];
8
10
  metadata?: Record<string, unknown>;
9
11
  }
10
- export interface GraphFile {
12
+ export interface GraphFileV1 {
11
13
  version: 1;
12
14
  owner?: {
13
15
  name?: string;
@@ -15,6 +17,49 @@ export interface GraphFile {
15
17
  };
16
18
  entities: GraphEntity[];
17
19
  }
20
+ export interface CanonicalEntity {
21
+ id: string;
22
+ type: CanonicalEntityKind;
23
+ name: string;
24
+ aliases?: string[];
25
+ summary?: string;
26
+ tags?: string[];
27
+ validFrom?: string;
28
+ validTo?: string;
29
+ metadata?: Record<string, unknown>;
30
+ }
31
+ export interface CanonicalEdge {
32
+ id: string;
33
+ fromId: string;
34
+ relation: CoreRelation;
35
+ toId: string;
36
+ role?: string;
37
+ context?: string;
38
+ validFrom?: string;
39
+ validTo?: string;
40
+ provenance?: {
41
+ sourceKind: 'user_approved' | 'migration' | 'import' | 'onboarding';
42
+ sourceId?: string;
43
+ evidenceHash?: string;
44
+ };
45
+ }
46
+ export interface GraphFileV2 {
47
+ version: 2;
48
+ ontology: {
49
+ id: 'brainbase-personal-os';
50
+ version: string;
51
+ releaseDigest: string;
52
+ };
53
+ owner?: {
54
+ id?: string;
55
+ name?: string;
56
+ summary?: string;
57
+ };
58
+ entities: CanonicalEntity[];
59
+ edges: CanonicalEdge[];
60
+ }
61
+ export type GraphFile = GraphFileV1 | GraphFileV2;
62
+ export type CanonicalGraphFile = GraphFile;
18
63
  export interface PersonalKgEntry {
19
64
  id: string;
20
65
  type: 'self' | 'work' | 'relationship' | 'value' | 'judgment' | 'experience' | 'sns_context';
@@ -60,4 +105,10 @@ export interface SearchResult {
60
105
  title: string;
61
106
  text: string;
62
107
  score: number;
108
+ canonicalEntityId?: string;
109
+ recordClass: 'canonical' | 'projection' | 'unresolved';
110
+ projectionOf?: string;
111
+ projectionSources?: Array<'relationships' | 'decisions'>;
112
+ relationPath?: string[];
113
+ authority: 'local_graph' | 'personal_kg' | 'legacy_relationships' | 'legacy_decisions';
63
114
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unson/brainbase-mcp",
3
- "version": "0.2.4",
3
+ "version": "0.3.1",
4
4
  "description": "Local-first Brainbase MCP server and personal onboarding kit.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -26,7 +26,8 @@
26
26
  "scripts": {
27
27
  "build": "tsc -p tsconfig.json",
28
28
  "test": "vitest run",
29
- "test:integration": "vitest run tests/mcp-contract.test.ts tests/ontology-cli.test.ts tests/npm-release-validation.integration.test.ts",
29
+ "test:integration": "vitest run tests/mcp-contract.test.ts tests/ontology-cli.test.ts tests/npm-release-validation.integration.test.ts tests/npm-consumer-smoke.integration.test.ts",
30
+ "test:consumer-smoke": "vitest run tests/npm-consumer-smoke.integration.test.ts",
30
31
  "test:integration:release-evidence": "vitest run tests/npm-release-validation.integration.test.ts tests/npm-prepublication-evidence.integration.test.ts",
31
32
  "test:e2e": "vitest run tests/e2e/brainbase-mcp-only-acceptance.spec.ts tests/e2e/story-brainbase-portable-ontology-kernel-acceptance.spec.ts",
32
33
  "docs:dev": "vitepress dev docs",
@@ -61,5 +62,5 @@
61
62
  "engines": {
62
63
  "node": ">=20"
63
64
  },
64
- "gitHead": "c40de6a3037cc6a67b2f2eab5d9487093193d314"
65
+ "gitHead": "0ff2753a492aef09235be2792570000ef03dfa4b"
65
66
  }